From b1384b9cce9d741954c8f81623dc35d8df34167c Mon Sep 17 00:00:00 2001 From: Oleksii Novikov Date: Mon, 10 Feb 2025 17:48:43 +0200 Subject: [PATCH] FINERACT-2081: Refactoring for reprocess transactions and regenerate repayment schedule invocations --- ...InterestPauseWritePlatformServiceImpl.java | 20 +- ....java => LoanAccountTransfersService.java} | 2 +- .../portfolio/loanaccount/domain/Loan.java | 79 ++-- .../domain/LoanAccountDomainService.java | 6 - .../domain/LoanAccountService.java | 29 ++ .../LoanDownPaymentHandlerService.java | 3 +- .../LoanDownPaymentHandlerServiceImpl.java | 18 +- .../service/LoanRefundService.java | 16 +- .../service/LoanScheduleService.java | 15 +- .../ReprocessLoanTransactionsService.java | 35 ++ .../ReprocessLoanTransactionsServiceImpl.java | 74 +++ .../AccountTransfersWritePlatformService.java | 3 +- ...ountTransfersWritePlatformServiceImpl.java | 10 - .../LoanAccountTransfersServiceImpl.java | 45 ++ .../domain/LoanAccountDomainServiceJpa.java | 160 ++----- .../LoanScheduleWritePlatformServiceImpl.java | 8 +- ...heduleRequestWritePlatformServiceImpl.java | 27 +- .../service/LoanAccountServiceImpl.java | 85 ++++ ...nAccrualActivityProcessingServiceImpl.java | 8 +- .../LoanChargeWritePlatformServiceImpl.java | 108 ++--- .../service/LoanDisbursementService.java | 7 +- ...WritePlatformServiceJpaRepositoryImpl.java | 427 ++++++------------ ...gressiveLoanInterestRefundServiceImpl.java | 5 +- .../reaging/LoanReAgingServiceImpl.java | 11 +- .../LoanReAmortizationServiceImpl.java | 4 +- .../starter/LoanAccountConfiguration.java | 74 +-- ...oanChargeWritePlatformServiceImplTest.java | 9 +- ...LoanDownPaymentHandlerServiceImplTest.java | 6 +- 28 files changed, 600 insertions(+), 694 deletions(-) rename fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/{AccountTransfersService.java => LoanAccountTransfersService.java} (95%) create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountService.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/LoanAccountTransfersServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccountServiceImpl.java diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java index 884ec8417..003d1126f 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/InterestPauseWritePlatformServiceImpl.java @@ -40,15 +40,12 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; -import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; -import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService; +import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.springframework.transaction.annotation.Transactional; @AllArgsConstructor @@ -58,9 +55,7 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite private final LoanTermVariationsRepository loanTermVariationsRepository; private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanAssembler loanAssembler; - private final LoanAccountDomainService loanAccountDomainService; - private final AccountTransfersService accountTransfersService; - private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; @Override public CommandProcessingResult createInterestPause(final ExternalId loanExternalId, final String startDateString, @@ -151,16 +146,9 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite loan); final LoanTermVariations savedVariation = loanTermVariationsRepository.saveAndFlush(variation); - loan.getLoanTermVariations().add(savedVariation); - final ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } + + reprocessLoanTransactionsService.reprocessTransactions(loan); return new CommandProcessingResultBuilder().withEntityId(savedVariation.getId()).build(); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/AccountTransfersService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/LoanAccountTransfersService.java similarity index 95% rename from fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/AccountTransfersService.java rename to fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/LoanAccountTransfersService.java index fd9672db6..0ecfe288b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/AccountTransfersService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/interestpauses/service/LoanAccountTransfersService.java @@ -20,7 +20,7 @@ package org.apache.fineract.portfolio.interestpauses.service; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; -public interface AccountTransfersService { +public interface LoanAccountTransfersService { void updateLoanTransaction(Long loanTransactionId, LoanTransaction newLoanTransaction); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 62177e5b3..50230e0cc 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.domain; +import static org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType.CUMULATIVE; import static org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType.PROGRESSIVE; import com.google.gson.JsonElement; @@ -829,7 +830,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return lastRepaymentDate; } - public void removeLoanCharge(final LoanCharge loanCharge) { + public Optional removeLoanCharge(final LoanCharge loanCharge) { final boolean removed = loanCharge.isActive(); if (removed) { loanCharge.setActive(false); @@ -848,10 +849,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { * Consider removing this block of code or logically completing it for the future by getting the list of * affected Transactions */ - reprocessTransactions(); + return Optional.ofNullable(reprocessTransactions()); } this.charges.remove(loanCharge); updateLoanSummaryDerivedFields(); + + return Optional.empty(); } private void removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(final LoanCharge loanCharge) { @@ -900,37 +903,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { .anyMatch(loanChargePaidBy -> loanChargePaidBy.getLoanCharge().equals(loanCharge)); } - public Map updateLoanCharge(final LoanCharge loanCharge, final JsonCommand command) { - final Map actualChanges = new LinkedHashMap<>(3); - - if (getActiveCharges().contains(loanCharge)) { - final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge); - final Map loanChargeChanges = loanCharge.update(command, amount); - actualChanges.putAll(loanChargeChanges); - updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); - } - - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); - if (!loanCharge.isDueAtDisbursement()) { - /* - * TODO Vishwas Currently we do not allow waiving updating loan charge after a loan is approved (hence there - * is no need to adjust any loan transactions). - * - * Consider removing this block of code or logically completing it for the future by getting the list of - * affected Transactions - */ - reprocessTransactions(); - } else { - // reprocess loan schedule based on charge been waived. - final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), getActiveCharges()); - } - - updateLoanSummaryDerivedFields(); - - return actualChanges; - } - public BigDecimal calculateAmountPercentageAppliedTo(final LoanCharge loanCharge) { if (loanCharge.isOverdueInstallmentCharge()) { return loanCharge.getAmountPercentageAppliedTo(); @@ -1881,10 +1853,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return getStatus().isClosedWrittenOff(); } - public boolean isClosedWithOutstandingAmountMarkedForReschedule() { - return getStatus().isClosedWithOutsandingAmountMarkedForReschedule(); - } - public boolean isCancelled() { return isRejected() || isWithdrawn(); } @@ -2696,20 +2664,25 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { /* * Probably this is buggy when a chargeback transaction happens */ - public void processPostDisbursementTransactions() { + public Optional processPostDisbursementTransactions() { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(); final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(); final List copyTransactions = new ArrayList<>(); - if (!allNonContraTransactionsPostDisbursement.isEmpty()) { - // 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)); - } - loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(), copyTransactions, getCurrency(), - getRepaymentScheduleInstallments(), getActiveCharges()); - updateLoanSummaryDerivedFields(); + 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) { @@ -2826,7 +2799,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { Money totalInterest = Money.zero(getCurrency()); Money feeCharges = Money.zero(getCurrency()); Money penaltyCharges = Money.zero(getCurrency()); - final Set compoundingDetails = null; List repaymentSchedule = getRepaymentScheduleInstallments(); for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) { totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(getCurrency())); @@ -3106,11 +3078,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return this.createStandingInstructionAtDisbursement != null && this.createStandingInstructionAtDisbursement; } - public List getLoanChargesByDueDate(LocalDate dueDate) { - return getLoanCharges( - loanCharge -> loanCharge.getDueLocalDate() != null && DateUtils.isEqual(loanCharge.getDueLocalDate(), dueDate)); - } - /** * @param predicate * filter of the charges @@ -3591,6 +3558,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return getLoanProductRelatedDetail().getLoanScheduleType() == PROGRESSIVE; } + public boolean isCumulativeSchedule() { + return getLoanProductRelatedDetail().getLoanScheduleType() == CUMULATIVE; + } + public boolean isChargeOffOnDate(final LocalDate onDate) { final LoanTransaction chargeOffTransaction = findChargedOffTransaction(); return (chargeOffTransaction == null) ? false : (chargeOffTransaction.getDateOf().compareTo(onDate) <= 0); @@ -3609,4 +3580,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { } return false; } + + public boolean hasChargeOffTransaction() { + return getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff); + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java index 2644d5a2a..8c42b5125 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainService.java @@ -74,12 +74,6 @@ public interface LoanAccountDomainService { boolean isRecoveryRepayment, String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, Boolean isHolidayValidationDone, boolean isLoanToLoanTransfer); - LoanTransaction saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction); - - Loan saveAndFlushLoanWithDataIntegrityViolationChecks(Loan loan); - - Loan saveLoanWithDataIntegrityViolationChecks(Loan loan); - LoanTransaction foreCloseLoan(Loan loan, LocalDate foreClourseDate, String noteText, ExternalId externalId, Map changes); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountService.java new file mode 100644 index 000000000..5b0e3ae81 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountService.java @@ -0,0 +1,29 @@ +/** + * 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.domain; + +public interface LoanAccountService { + + LoanTransaction saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction); + + Loan saveAndFlushLoanWithDataIntegrityViolationChecks(Loan loan); + + Loan saveLoanWithDataIntegrityViolationChecks(Loan loan); + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java index 01f71bba9..e2e7b38e2 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java @@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.service; import org.apache.fineract.infrastructure.core.api.JsonCommand; 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.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; @@ -30,7 +29,7 @@ public interface LoanDownPaymentHandlerService { LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command, LoanTransaction disbursementTransaction, Loan loan); - ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(Loan loan, LoanTransaction newTransactionDetail, + void handleRepaymentOrRecoveryOrWaiverTransaction(Loan loan, LoanTransaction newTransactionDetail, LoanLifecycleStateMachine loanLifecycleStateMachine, LoanTransaction transactionForAdjustment, ScheduleGeneratorDTO scheduleGeneratorDTO); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java index f6819f4dd..139603d52 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java @@ -36,7 +36,6 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; 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.LoanEvent; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; @@ -48,7 +47,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Mon import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException; import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException; -import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.serialization.LoanDownPaymentTransactionValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanRefundValidator; @@ -62,6 +60,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler private final LoanScheduleService loanScheduleService; private final LoanRefundService loanRefundService; private final LoanRefundValidator loanRefundValidator; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; @Override public LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command, @@ -77,11 +76,9 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler } @Override - public ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final Loan loan, final LoanTransaction loanTransaction, + public void handleRepaymentOrRecoveryOrWaiverTransaction(final Loan loan, final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction, final ScheduleGeneratorDTO scheduleGeneratorDTO) { - ChangedTransactionDetail changedTransactionDetail = null; - if (loanTransaction.isRecoveryRepayment()) { loanLifecycleStateMachine.transition(LoanEvent.LOAN_RECOVERY_PAYMENT, loan); } @@ -155,15 +152,12 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler } } if (reprocess) { - if (loan.isInterestBearingAndInterestRecalculationEnabled() - && !loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE)) { + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + } else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - changedTransactionDetail = loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); } loan.updateLoanSummaryDerivedFields(); @@ -185,8 +179,6 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler throw new InvalidLoanStateTransitionException("transaction", "amount.exceeds.threshold", errorMessage); } } - - return changedTransactionDetail; } private LoanTransaction handleDownPayment(final Loan loan, final LoanTransaction disbursementTransaction, final JsonCommand command, diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java index d9fd097b1..b41b8b599 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java @@ -65,7 +65,7 @@ public class LoanRefundService { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); - handleRefundTransaction(loan, loanTransaction, loanLifecycleStateMachine, null); + handleRefundTransaction(loan, loanTransaction, loanLifecycleStateMachine); } public void creditBalanceRefund(final Loan loan, final LoanTransaction newCreditBalanceRefundTransaction, @@ -88,7 +88,7 @@ public class LoanRefundService { } private void handleRefundTransaction(final Loan loan, final LoanTransaction loanTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) { + final LoanLifecycleStateMachine loanLifecycleStateMachine) { loanLifecycleStateMachine.transition(LoanEvent.LOAN_REFUND, loan); loanTransaction.updateLoan(loan); @@ -104,18 +104,12 @@ public class LoanRefundService { final LocalDate loanTransactionDate = extractTransactionDate(loan, loanTransaction); loanRefundValidator.validateTransactionDateNotInFuture(loanTransactionDate); - loanRefundValidator.validateTransactionAmountThreshold(loan, adjustedTransaction); + loanRefundValidator.validateTransactionAmountThreshold(loan, null); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor(); - // If it's a refund - if (adjustedTransaction == null) { - loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, - new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); - } else { - loan.reprocessTransactions(); - } + loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loan.getCurrency(), + loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); loan.updateLoanSummaryDerivedFields(); loan.doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java index c936e5308..2eab5a644 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java @@ -24,7 +24,6 @@ import java.util.Set; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.service.DateUtils; 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.loanschedule.data.LoanScheduleDTO; @@ -35,6 +34,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul public class LoanScheduleService { private final LoanChargeService loanChargeService; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; /** * Ability to regenerate the repayment schedule based on the loans current details/state. @@ -59,10 +59,10 @@ public class LoanScheduleService { } else { regenerateRepaymentSchedule(loan, generatorDTO); } - loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); } - public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final Loan loan, final ScheduleGeneratorDTO generatorDTO, + public void recalculateScheduleFromLastTransaction(final Loan loan, final ScheduleGeneratorDTO generatorDTO, final List existingTransactionIds, final List existingReversedTransactionIds) { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); @@ -73,7 +73,8 @@ public class LoanScheduleService { regenerateRepaymentSchedule(loan, generatorDTO); } } - return loan.reprocessTransactions(); + + reprocessLoanTransactionsService.reprocessTransactions(loan); } public void regenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) { @@ -106,13 +107,11 @@ public class LoanScheduleService { } } } - loan.processPostDisbursementTransactions(); } - public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, - final ScheduleGeneratorDTO generatorDTO) { + public void handleRegenerateRepaymentScheduleWithInterestRecalculation(final Loan loan, final ScheduleGeneratorDTO generatorDTO) { regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO); - return loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java new file mode 100644 index 000000000..bc84ad804 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.service; + +import java.time.LocalDate; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; + +public interface ReprocessLoanTransactionsService { + + void reprocessTransactions(Loan loan); + + void reprocessTransactionsWithPostTransactionChecks(Loan loan, LocalDate transactionDate); + + void processPostDisbursementTransactions(Loan loan); + + void removeLoanCharge(Loan loan, LoanCharge loanCharge); + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java new file mode 100644 index 000000000..d29867e52 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.service; + +import java.time.LocalDate; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService; +import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransactionsService { + + private final LoanAccountService loanAccountService; + private final LoanAccountTransfersService loanAccountTransfersService; + private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService; + + @Override + public void reprocessTransactions(final Loan loan) { + final ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); + if (changedTransactionDetail != null) { + handleChangedDetail(changedTransactionDetail); + } + } + + @Override + public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) { + final ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactionsWithPostTransactionChecks(transactionDate); + if (changedTransactionDetail != null) { + handleChangedDetail(changedTransactionDetail); + } + } + + @Override + public void processPostDisbursementTransactions(final Loan loan) { + loan.processPostDisbursementTransactions().ifPresent(this::handleChangedDetail); + } + + @Override + public void removeLoanCharge(final Loan loan, final LoanCharge loanCharge) { + loan.removeLoanCharge(loanCharge).ifPresent(this::handleChangedDetail); + } + + private void handleChangedDetail(final ChangedTransactionDetail changedTransactionDetail) { + for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); + loanAccountTransfersService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); + } + replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformService.java index db0e759d1..20210dd76 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformService.java @@ -24,9 +24,8 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.data.AccountTransferDTO; import org.apache.fineract.portfolio.account.domain.AccountTransferDetails; -import org.apache.fineract.portfolio.interestpauses.service.AccountTransfersService; -public interface AccountTransfersWritePlatformService extends AccountTransfersService { +public interface AccountTransfersWritePlatformService { CommandProcessingResult create(JsonCommand command); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java index 88e6d1650..6ce295331 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -502,16 +502,6 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer return accountTransferDetails; } - @Override - @Transactional - public void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { - final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); - if (transferTransaction != null) { - transferTransaction.updateToLoanTransaction(newLoanTransaction); - this.accountTransferRepository.save(transferTransaction); - } - } - private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/LoanAccountTransfersServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/LoanAccountTransfersServiceImpl.java new file mode 100644 index 000000000..1e3531423 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/LoanAccountTransfersServiceImpl.java @@ -0,0 +1,45 @@ +/** + * 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.account.service; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.portfolio.account.domain.AccountTransferRepository; +import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction; +import org.apache.fineract.portfolio.interestpauses.service.LoanAccountTransfersService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class LoanAccountTransfersServiceImpl implements LoanAccountTransfersService { + + private final AccountTransferRepository accountTransferRepository; + + @Override + @Transactional + public void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { + final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); + if (transferTransaction != null) { + transferTransaction.updateToLoanTransaction(newLoanTransaction); + this.accountTransferRepository.save(transferTransaction); + } + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 3318e6a89..91b90cf75 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -32,13 +32,10 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; -import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; -import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; -import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.MathUtil; @@ -77,9 +74,7 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; -import org.apache.fineract.portfolio.account.domain.AccountTransferRepository; import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction; -import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction; import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; import org.apache.fineract.portfolio.client.domain.Client; @@ -99,7 +94,6 @@ 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.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanDownPaymentTransactionValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanForeclosureValidator; @@ -114,7 +108,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerS import org.apache.fineract.portfolio.loanaccount.service.LoanRefundService; import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; -import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService; +import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; import org.apache.fineract.portfolio.note.domain.Note; import org.apache.fineract.portfolio.note.domain.NoteRepository; @@ -122,8 +116,6 @@ import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.orm.jpa.JpaSystemException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -141,7 +133,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final NoteRepository noteRepository; - private final AccountTransferRepository accountTransferRepository; private final BusinessEventNotifierService businessEventNotifierService; private final LoanUtilService loanUtilService; private final StandingInstructionRepository standingInstructionRepository; @@ -150,7 +141,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final DelinquencyWritePlatformService delinquencyWritePlatformService; private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; private final ExternalIdFactory externalIdFactory; - private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService; private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper; private final DelinquencyReadPlatformService delinquencyReadPlatformService; @@ -165,6 +155,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final LoanDownPaymentHandlerService loanDownPaymentHandlerService; private final LoanChargeValidator loanChargeValidator; private final LoanRefundService loanRefundService; + private final LoanAccountService loanAccountService; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; @Transactional @Override @@ -264,30 +256,16 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanDownPaymentTransactionValidator.validateRepaymentTypeAccountStatus(loan, newRepaymentTransaction, event); loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, event, newRepaymentTransaction.getTransactionDate()); - final ChangedTransactionDetail changedTransactionDetail = makeRepayment(loan, newRepaymentTransaction, - defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); + makeRepayment(loan, newRepaymentTransaction, defaultLoanLifecycleStateMachine, existingTransactionIds, + existingReversedTransactionIds, scheduleGeneratorDTO); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.reprocessExistingAccruals(loan); loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); } - saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction); - - /*** - * TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the - * time being, not a major issue for now as this loop is entered only in edge cases (when a payment is made - * before the latest payment recorded against the loan) - ***/ - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } - loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction); + loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); if (StringUtils.isNotBlank(noteText)) { final Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText); @@ -395,50 +373,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { return repaymentEvent; } - @Override - public LoanTransaction saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction) { - try { - return this.loanTransactionRepository.saveAndFlush(newRepaymentTransaction); - } catch (final JpaSystemException | DataIntegrityViolationException e) { - raiseValidationExceptionForUniqueConstraintViolation(e); - throw e; - } - } - - @Override - public Loan saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) { - try { - return this.loanRepositoryWrapper.saveAndFlush(loan); - } catch (final JpaSystemException | DataIntegrityViolationException e) { - raiseValidationExceptionForUniqueConstraintViolation(e); - throw e; - } - } - - @Override - public Loan saveLoanWithDataIntegrityViolationChecks(final Loan loan) { - try { - return this.loanRepositoryWrapper.save(loan); - } catch (final JpaSystemException | DataIntegrityViolationException e) { - raiseValidationExceptionForUniqueConstraintViolation(e); - throw e; - } - } - - private void raiseValidationExceptionForUniqueConstraintViolation(Exception e) { - final Throwable realCause = e.getCause(); - final List dataValidationErrors = new ArrayList<>(); - final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); - if (realCause.getMessage().toLowerCase().contains("external_id_unique") || realCause.getMessage() - .contains("duplicate key value violates unique constraint \"m_loan_transaction_external_id_key\"")) { - baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique"); - } - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", - dataValidationErrors, e); - } - } - @Override @Transactional public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate, @@ -487,8 +421,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanChargeService.makeChargePayment(loan, chargeId, defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, newPaymentTransaction, installmentNumber); } - saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction); - saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction); + loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); if (StringUtils.isNotBlank(noteText)) { final Note note = Note.loanTransactionNote(loan, newPaymentTransaction, noteText); @@ -579,7 +513,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanRefundService.makeRefund(loan, newRefundTransaction, defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds); - saveLoanTransactionWithDataIntegrityViolationChecks(newRefundTransaction); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newRefundTransaction); this.loanRepositoryWrapper.saveAndFlush(loan); if (StringUtils.isNotBlank(noteText)) { @@ -628,8 +562,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { disbursementTransaction.updateLoan(loan); loan.addLoanTransaction(disbursementTransaction); - saveLoanTransactionWithDataIntegrityViolationChecks(disbursementTransaction); - saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(disbursementTransaction); + loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); if (StringUtils.isNotBlank(noteText)) { final Note note = Note.loanTransactionNote(loan, disbursementTransaction, noteText); @@ -651,7 +585,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction, "reversed"); loanTransaction.reverse(); - saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(loanTransaction); } @Override @@ -686,14 +620,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } } - private void updateLoanTransferTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { - final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); - if (transferTransaction != null) { - transferTransaction.updateToLoanTransaction(newLoanTransaction); - this.accountTransferRepository.save(transferTransaction); - } - } - @Override public LoanTransaction creditBalanceRefund(final Loan loan, final LocalDate transactionDate, final BigDecimal transactionAmount, final String noteText, final ExternalId externalId, PaymentDetail paymentDetail) { @@ -841,36 +767,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanForeclosureValidator.validateForForeclosure(loan, payment.getTransactionDate()); } loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_FORECLOSURE); - final ChangedTransactionDetail changedTransactionDetail = handleForeClosureTransactions(loan, payment, - defaultLoanLifecycleStateMachine, scheduleGeneratorDTO); + handleForeClosureTransactions(loan, payment, defaultLoanLifecycleStateMachine, scheduleGeneratorDTO); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); } - /*** - * TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the - * time being, not a major issue for now as this loop is entered only in edge cases (when a payment is made - * before the latest payment recorded against the loan) - ***/ - for (LoanTransaction newTransaction : newTransactions) { - saveLoanTransactionWithDataIntegrityViolationChecks(newTransaction); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newTransaction); transactionIds.add(newTransaction.getId()); } changes.put("transactions", transactionIds); changes.put("eventAmount", payPrincipal.getAmount().negate()); - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } - loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); if (StringUtils.isNotBlank(noteText)) { changes.put("note", noteText); @@ -978,36 +889,23 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); } } else { - if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + } else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } loan.getLoanTransactions().add(refundTransaction); if (interestRefundTransaction != null) { loan.addLoanTransaction(interestRefundTransaction); } - ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); + + reprocessLoanTransactionsService.reprocessTransactions(loan); // Store and flush newly created transaction to generate PK - saveLoanTransactionWithDataIntegrityViolationChecks(refundTransaction); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(refundTransaction); if (interestRefundTransaction != null) { - saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction); } - /* - * TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for - * the time being, not a major issue for now as this loop is entered only in edge cases (when a payment is - * made before the latest payment recorded against the loan) - */ - // Apply reprocessed transactions (if applicable) - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); } loan.updateLoanSummaryDerivedFields(); @@ -1076,7 +974,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanRefundRequest.getPrincipal(), loanRefundRequest.getInterest(), loanRefundRequest.getFeeCharges(), loanRefundRequest.getPenaltyCharges(), paymentDetail, transactionDate, externalIdFactory.create()); interestRefundTransaction.updateLoan(loan); - saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction); loan.addLoanTransaction(interestRefundTransaction); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false); @@ -1154,21 +1052,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } @SuppressWarnings("null") - private ChangedTransactionDetail makeRepayment(final Loan loan, final LoanTransaction repaymentTransaction, + private void makeRepayment(final Loan loan, final LoanTransaction repaymentTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loan, repaymentTransaction, "created"); existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); - return loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, repaymentTransaction, - loanLifecycleStateMachine, null, scheduleGeneratorDTO); + loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, repaymentTransaction, loanLifecycleStateMachine, + null, scheduleGeneratorDTO); } - private ChangedTransactionDetail handleForeClosureTransactions(final Loan loan, final LoanTransaction repaymentTransaction, + private void handleForeClosureTransactions(final Loan loan, final LoanTransaction repaymentTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final ScheduleGeneratorDTO scheduleGeneratorDTO) { loan.setLoanSubStatus(LoanSubStatus.FORECLOSED.getValue()); - return loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, repaymentTransaction, - loanLifecycleStateMachine, null, scheduleGeneratorDTO); + loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, repaymentTransaction, loanLifecycleStateMachine, + null, scheduleGeneratorDTO); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java index bcc6373b3..2ba6e2349 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleWritePlatformServiceImpl.java @@ -33,7 +33,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; @@ -47,13 +47,13 @@ import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePlatformService { - private final LoanAccountDomainService loanAccountDomainService; private final LoanAssembler loanAssembler; private final LoanScheduleAssembler loanScheduleAssembler; private final LoanUtilService loanUtilService; private final BusinessEventNotifierService businessEventNotifierService; private final LoanAccrualsProcessingService loanAccrualsProcessingService; private final LoanScheduleService loanScheduleService; + private final LoanAccountService loanAccountService; @Override public CommandProcessingResult addLoanScheduleVariations(final Long loanId, final JsonCommand command) { @@ -64,7 +64,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl } loanScheduleAssembler.assempleVariableScheduleFrom(loan, command.json()); - loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan); + loanAccountService.saveLoanWithDataIntegrityViolationChecks(loan); final Map changes = new HashMap<>(); List newVariationsData = new ArrayList<>(); List modifiedVariations = loan.getLoanTermVariations(); @@ -102,7 +102,7 @@ public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePl ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); loanAccrualsProcessingService.reprocessExistingAccruals(loan); - loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan); + loanAccountService.saveLoanWithDataIntegrityViolationChecks(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanScheduleVariationsDeletedBusinessEvent(loan)); return new CommandProcessingResultBuilder() // .withLoanId(loanId) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 9837dc808..cafa08562 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -47,12 +47,9 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; -import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; 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.LoanAccountDomainService; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; @@ -62,7 +59,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToT import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations; -import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator; @@ -82,7 +78,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingS import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; -import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService; +import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.useradministration.domain.AppUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -98,6 +94,7 @@ import org.springframework.transaction.annotation.Transactional; public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanRescheduleRequestWritePlatformService { private static final Logger LOG = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class); + private static final DefaultScheduledDateGenerator DEFAULT_SCHEDULED_DATE_GENERATOR = new DefaultScheduledDateGenerator(); private final CodeValueRepositoryWrapper codeValueRepositoryWrapper; private final PlatformSecurityContext platformSecurityContext; @@ -112,15 +109,12 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche private final LoanUtilService loanUtilService; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanScheduleGeneratorFactory loanScheduleFactory; - private final AccountTransfersWritePlatformService accountTransfersWritePlatformService; - private static final DefaultScheduledDateGenerator DEFAULT_SCHEDULED_DATE_GENERATOR = new DefaultScheduledDateGenerator(); - private final LoanAccountDomainService loanAccountDomainService; private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository; - private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService; private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; private final BusinessEventNotifierService businessEventNotifierService; private final LoanAccrualsProcessingService loanAccrualsProcessingService; private final LoanChargeService loanChargeService; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; /** * create a new instance of the LoanRescheduleRequest object from the JsonCommand object and persist @@ -440,7 +434,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche } loanAccrualsProcessingService.reprocessExistingAccruals(loan); loanChargeService.recalculateAllCharges(loan); - ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); this.loanRepaymentScheduleHistoryRepository.saveAll(loanRepaymentScheduleHistoryList); @@ -450,19 +444,6 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche // update the status of the request loanRescheduleRequest.approve(appUser, approvedOnDate); - /*** - * TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for - * the time being, not a major issue for now as this loop is entered only in edge cases (when a adjustment - * is made before the latest payment recorded against the loan) - ***/ - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccountServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccountServiceImpl.java new file mode 100644 index 000000000..b0e6b2dd1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccountServiceImpl.java @@ -0,0 +1,85 @@ +/** + * 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.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.jpa.JpaSystemException; + +@RequiredArgsConstructor +public class LoanAccountServiceImpl implements LoanAccountService { + + private final LoanRepositoryWrapper loanRepositoryWrapper; + private final LoanTransactionRepository loanTransactionRepository; + + @Override + public LoanTransaction saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction) { + try { + return this.loanTransactionRepository.saveAndFlush(newRepaymentTransaction); + } catch (final JpaSystemException | DataIntegrityViolationException e) { + raiseValidationExceptionForUniqueConstraintViolation(e); + throw e; + } + } + + @Override + public Loan saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) { + try { + return this.loanRepositoryWrapper.saveAndFlush(loan); + } catch (final JpaSystemException | DataIntegrityViolationException e) { + raiseValidationExceptionForUniqueConstraintViolation(e); + throw e; + } + } + + @Override + public Loan saveLoanWithDataIntegrityViolationChecks(final Loan loan) { + try { + return this.loanRepositoryWrapper.save(loan); + } catch (final JpaSystemException | DataIntegrityViolationException e) { + raiseValidationExceptionForUniqueConstraintViolation(e); + throw e; + } + } + + private void raiseValidationExceptionForUniqueConstraintViolation(Exception e) { + final Throwable realCause = e.getCause(); + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); + if (realCause.getMessage().toLowerCase().contains("external_id_unique") || realCause.getMessage() + .contains("duplicate key value violates unique constraint \"m_loan_transaction_external_id_key\"")) { + baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique"); + } + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", + dataValidationErrors, e); + } + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java index 310c21a6d..49c2b1498 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java @@ -34,7 +34,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; @@ -54,7 +54,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi private final ExternalIdFactory externalIdFactory; private final BusinessEventNotifierService businessEventNotifierService; private final LoanTransactionAssembler loanTransactionAssembler; - private final LoanAccountDomainService loanAccountDomainService; + private final LoanAccountService loanAccountService; @Override @Transactional(propagation = Propagation.REQUIRES_NEW) @@ -207,7 +207,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations()); newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction, loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED)); - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction); loan.addLoanTransaction(newLoanTransaction); LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction); @@ -242,7 +242,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi private LoanTransaction makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanTransaction newAccrualActivityTransaction) { businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan)); - newAccrualActivityTransaction = loanAccountDomainService + newAccrualActivityTransaction = loanAccountService .saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction); loan.addLoanTransaction(newAccrualActivityTransaction); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index dff251e36..c6f461029 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -92,9 +92,9 @@ import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData; 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.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; @@ -165,7 +165,6 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo private final ExternalIdFactory externalIdFactory; private final AccountTransferDetailRepository accountTransferDetailRepository; private final LoanChargeAssembler loanChargeAssembler; - private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService; private final PaymentDetailWritePlatformService paymentDetailWritePlatformService; private final NoteRepository noteRepository; private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; @@ -173,6 +172,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator; private final LoanChargeValidator loanChargeValidator; private final LoanScheduleService loanScheduleService; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private final LoanAccountService loanAccountService; private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) { return DateUtils.isAfter(loanCharge.getDueDate(), e.getFromDate()) && !DateUtils.isAfter(loanCharge.getDueDate(), e.getDueDate()); @@ -287,25 +288,18 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo } if (reprocessRequired) { - if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - ChangedTransactionDetail changedTransactionDetail = overpaidReprocess - ? loan.reprocessTransactionsWithPostTransactionChecks(transactionDate) - : loan.reprocessTransactions(); - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); + if (overpaidReprocess) { + reprocessLoanTransactionsService.reprocessTransactionsWithPostTransactionChecks(loan, transactionDate); + } else { + reprocessLoanTransactionsService.reprocessTransactions(loan); } - loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + + loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); } if (loan.isInterestBearingAndInterestRecalculationEnabled() && isAppliedOnBackDate @@ -469,7 +463,23 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo businessEventNotifierService.notifyPreBusinessEvent(new LoanUpdateChargeBusinessEvent(loanCharge)); loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge); - final Map changes = loan.updateLoanCharge(loanCharge, command); + + final Map changes = new LinkedHashMap<>(3); + if (loan.getActiveCharges().contains(loanCharge)) { + final BigDecimal amount = loan.calculateAmountPercentageAppliedTo(loanCharge); + final Map loanChargeChanges = loanCharge.update(command, amount); + changes.putAll(loanChargeChanges); + loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement()); + } + + if (!loanCharge.isDueAtDisbursement()) { + reprocessLoanTransactionsService.reprocessTransactions(loan); + } else { + // reprocess loan schedule based on charge been waived. + final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); + wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(), + loan.getActiveCharges()); + } this.loanRepositoryWrapper.save(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanUpdateChargeBusinessEvent(loanCharge)); @@ -606,7 +616,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge); loanChargeValidator.validateLoanChargeIsNotWaived(loan, loanCharge); - loan.removeLoanCharge(loanCharge); + reprocessLoanTransactionsService.removeLoanCharge(loan, loanCharge); this.loanRepositoryWrapper.save(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanDeleteChargeBusinessEvent(loanCharge)); return new CommandProcessingResultBuilder() // @@ -820,23 +830,12 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo if (reprocessRequired) { addInstallmentIfPenaltyAppliedAfterLastDueDate(loan, lastChargeDate); - if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings() - .entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } - loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + reprocessLoanTransactionsService.reprocessTransactions(loan); + loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); } if (loan.isInterestBearingAndInterestRecalculationEnabled() && runInterestRecalculation @@ -866,22 +865,20 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo .determineProcessor(loan.transactionProcessingStrategy()); loan.addLoanTransaction(loanChargeAdjustmentTransaction); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { - if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); } else { loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanChargeAdjustmentTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); } - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(loanChargeAdjustmentTransaction); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(loanChargeAdjustmentTransaction); loan.updateLoanSummaryAndStatus(); - loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); final Map accountingBridgeData = loan.deriveAccountingBridgeData(loan.getCurrency().getCode(), existingTransactionIds, existingReversedTransactionIds, false); @@ -1176,20 +1173,10 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isChargedOff()) { final List existingTransactionIds = loan.findExistingTransactionIds(); ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); - ChangedTransactionDetail changedTransactionDetail = loanScheduleService - .handleRegenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO); + loanScheduleService.handleRegenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO); loanAccrualsProcessingService.reprocessExistingAccruals(loan); loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); - - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } - loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); + loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); } return loan; @@ -1456,29 +1443,16 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo waiveLoanChargeTransaction.getAmount(loan.getCurrency()).getAmount(), loanInstallmentNumber); waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); loan.addLoanTransaction(waiveLoanChargeTransaction); - if (loan.isInterestBearingAndInterestRecalculationEnabled() && DateUtils.isBefore(loanCharge.getDueLocalDate(), businessDate)) { + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled() + && DateUtils.isBefore(loanCharge.getDueLocalDate(), businessDate)) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + } else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } // Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule // to be reprocessed. if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loan.getCurrency())) { - /* - * TODO Vishwas Currently we do not allow waiving fully paid loan charge and waiving partially paid loan - * charges only waives the remaining amount. - * - * Consider removing this block of code or logically completing it for the future by getting the list of - * affected Transactions - */ - if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { - loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); - } - loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); } else { // reprocess loan schedule based on charge been waived. final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java index e45bf6832..b8b175f99 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java @@ -58,6 +58,7 @@ public class LoanDisbursementService { private final LoanChargeValidator loanChargeValidator; private final LoanDisbursementValidator loanDisbursementValidator; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; public void updateDisbursementDetails(final Loan loan, final JsonCommand jsonCommand, final Map actualChanges) { final List disbursementList = loan.fetchDisbursementIds(); @@ -262,7 +263,7 @@ public class LoanDisbursementService { for (LoanCharge loanCharge : tempCharges) { loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge); loanChargeValidator.validateLoanChargeIsNotWaived(loan, loanCharge); - loan.removeLoanCharge(loanCharge); + reprocessLoanTransactionsService.removeLoanCharge(loan, loanCharge); } loan.getTrancheCharges().clear(); } else { @@ -272,7 +273,7 @@ public class LoanDisbursementService { if (loan.getCharges().contains(deleteCharge)) { loanChargeValidator.validateLoanIsNotClosed(loan, deleteCharge); loanChargeValidator.validateLoanChargeIsNotWaived(loan, deleteCharge); - loan.removeLoanCharge(deleteCharge); + reprocessLoanTransactionsService.removeLoanCharge(loan, deleteCharge); } } } @@ -295,6 +296,6 @@ public class LoanDisbursementService { loanChargeValidator.validateLoanChargeIsNotWaived(loan, charge); // return true; // }) // - .forEach(loan::removeLoanCharge); + .forEach(loanCharge -> reprocessLoanTransactionsService.removeLoanCharge(loan, loanCharge)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 95ae3d15a..cf3a0f9eb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -123,9 +123,7 @@ import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepositor import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository; import org.apache.fineract.portfolio.account.domain.AccountTransferDetails; import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType; -import org.apache.fineract.portfolio.account.domain.AccountTransferRepository; import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction; -import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction; import org.apache.fineract.portfolio.account.domain.AccountTransferType; import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; @@ -153,11 +151,11 @@ import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.command.LoanUpdateCommand; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; -import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; @@ -267,11 +265,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final LoanLifecycleStateMachine loanLifecycleStateMachine; private final LoanAccountLockService loanAccountLockService; private final ExternalIdFactory externalIdFactory; - private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService; private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; private final ErrorHandler errorHandler; private final LoanDownPaymentHandlerService loanDownPaymentHandlerService; - private final AccountTransferRepository accountTransferRepository; private final LoanTransactionAssembler loanTransactionAssembler; private final LoanAccrualsProcessingService loanAccrualsProcessingService; private final LoanOfficerValidator loanOfficerValidator; @@ -280,6 +276,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final LoanScheduleService loanScheduleService; private final LoanChargeValidator loanChargeValidator; private final LoanOfficerService loanOfficerService; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private final LoanAccountService loanAccountService; @Transactional @Override @@ -429,8 +427,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (loan.isInterestBearingAndInterestRecalculationEnabled() || downPaymentEnabled) { createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO); } - ChangedTransactionDetail changedTransactionDetail = disburseLoan(command, isPaymentTypeApplicableForDisbursementCharge, - paymentDetail, loan, currentUser, changes, scheduleGeneratorDTO); + disburseLoan(command, isPaymentTypeApplicableForDisbursementCharge, paymentDetail, loan, currentUser, changes, + scheduleGeneratorDTO); loan.adjustNetDisbursalAmount(amountToDisburse.getAmount()); loanAccrualsProcessingService.reprocessExistingAccruals(loan); @@ -444,14 +442,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (disbursementTransaction != null) { loanTransactionRepository.saveAndFlush(disbursementTransaction); } - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } + if (loan.isAutoRepaymentForDownPaymentEnabled() && !isWithoutAutoPayment) { // updating linked savings account for auto down payment transaction for disbursement to savings account if (isAccountTransfer && loan.shouldCreateStandingInstructionAtDisbursement()) { @@ -569,9 +560,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } - private ChangedTransactionDetail disburseLoan(JsonCommand command, boolean isPaymentTypeApplicableForDisbursementCharge, - PaymentDetail paymentDetail, Loan loan, AppUser currentUser, Map changes, - ScheduleGeneratorDTO scheduleGeneratorDTO) { + private void disburseLoan(JsonCommand command, boolean isPaymentTypeApplicableForDisbursementCharge, PaymentDetail paymentDetail, + Loan loan, AppUser currentUser, Map changes, ScheduleGeneratorDTO scheduleGeneratorDTO) { final PaymentDetail paymentDetail1 = isPaymentTypeApplicableForDisbursementCharge ? paymentDetail : null; final LocalDate actualDisbursementDate1 = command.localDateValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE); @@ -584,9 +574,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf boolean disbursementMissedParam = loan.isDisbursementMissed(); LocalDate firstInstallmentDueDate = loan.fetchRepaymentScheduleInstallment(1).getDueDate(); - if ((loan.isInterestBearingAndInterestRecalculationEnabled() + if ((loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled() && (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || disbursementMissedParam))) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); + } else { + loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement()); @@ -612,18 +604,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.addLoanTransaction(interestAppliedTransaction); } - ChangedTransactionDetail changedTransactionDetail = null; - if (loan.getLoanProduct().isMultiDisburseLoan()) { + if (loan.getLoanProduct().isMultiDisburseLoan() || loan.isProgressiveSchedule()) { final List allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing(); if (!allNonContraTransactionsPostDisbursement.isEmpty()) { - changedTransactionDetail = loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); } loan.updateLoanSummaryDerivedFields(); } loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, loan); changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus())); - return changedTransactionDetail; } private void updatePostDatedChecks(Set postDatedChecks) { @@ -784,7 +774,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_DISBURSED); updateLoanCounters(loan, actualDisbursementDate); boolean canDisburse = loan.canDisburse(); - ChangedTransactionDetail changedTransactionDetail = null; if (canDisburse) { Money amountBeforeAdjust = loan.getPrincipal(); Money disburseAmount = loanDisbursementService.adjustDisburseAmount(loan, command, actualDisbursementDate); @@ -813,8 +802,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (loan.isInterestBearingAndInterestRecalculationEnabled() || downPaymentEnabled) { createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO); } - changedTransactionDetail = disburseLoan(command, configurationDomainService.isPaymentTypeApplicableForDisbursementCharge(), - paymentDetail, loan, currentUser, changes, scheduleGeneratorDTO); + disburseLoan(command, configurationDomainService.isPaymentTypeApplicableForDisbursementCharge(), paymentDetail, loan, + currentUser, changes, scheduleGeneratorDTO); loanAccrualsProcessingService.reprocessExistingAccruals(loan); @@ -825,17 +814,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } if (!changes.isEmpty()) { - createNote(loan, command, changes); - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings() - .entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); @@ -1049,14 +1028,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return statusChanged; } - private void updateLoanTransferTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { - final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); - if (transferTransaction != null) { - transferTransaction.updateToLoanTransaction(newLoanTransaction); - this.accountTransferRepository.save(transferTransaction); - } - } - // TODO check public boolean isChronologicallyTheLatestTransaction(final LoanTransaction loanTransaction, final List loanTransactions) { @@ -1077,8 +1048,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return isChronologicallyLatestRepaymentOrWaiver; } - private ChangedTransactionDetail recalculateLoanWithInterestPaymentWaiverTxn(Loan loan, - LoanTransaction newInterestPaymentWaiverTransaction) { + private void recalculateLoanWithInterestPaymentWaiverTxn(Loan loan, LoanTransaction newInterestPaymentWaiverTransaction) { LocalDate recalculateFrom = null; LocalDate transactionDate = newInterestPaymentWaiverTransaction.getTransactionDate(); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { @@ -1086,7 +1056,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null); - ChangedTransactionDetail changedTransactionDetail = null; newInterestPaymentWaiverTransaction.updateLoan(loan); final boolean isTransactionChronologicallyLatest = loan @@ -1126,37 +1095,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } if (reprocess) { - changedTransactionDetail = reprocessChangedLoanTransactions(loan, loanRepaymentScheduleTransactionProcessor, - scheduleGeneratorDTO); + reprocessChangedLoanTransactions(loan, scheduleGeneratorDTO); } loan.updateLoanSummaryDerivedFields(); doPostLoanTransactionChecks(loan, newInterestPaymentWaiverTransaction.getTransactionDate(), loanLifecycleStateMachine); - - return changedTransactionDetail; } - private ChangedTransactionDetail reprocessChangedLoanTransactions(Loan loan, - LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - ScheduleGeneratorDTO scheduleGeneratorDTO) { - if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + private void reprocessChangedLoanTransactions(Loan loan, ScheduleGeneratorDTO scheduleGeneratorDTO) { + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - - } else if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + } else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.reprocessExistingAccruals(loan); loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); } - - return changedTransactionDetail; } @Transactional @@ -1179,14 +1138,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf LoanTransaction newInterestPaymentWaiverTransaction = loanTransactionAssembler.assembleTransactionAndCalculateChanges(loan, command, changes); - ChangedTransactionDetail changedTransactionDetail = recalculateLoanWithInterestPaymentWaiverTxn(loan, - newInterestPaymentWaiverTransaction); + recalculateLoanWithInterestPaymentWaiverTxn(loan, newInterestPaymentWaiverTransaction); loanTransactionValidator.validateLoanTransactionInterestPaymentWaiverAfterRecalculation(loan); - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newInterestPaymentWaiverTransaction); - - saveChangedTransactionDetail(changedTransactionDetail); + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newInterestPaymentWaiverTransaction); loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -1230,17 +1186,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } - private void saveChangedTransactionDetail(ChangedTransactionDetail changedTransactionDetail) { - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } - } - @Transactional @Override public CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(final LoanTransactionType repaymentTransactionType, @@ -1451,9 +1396,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loanTransactionValidator.validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); - final ChangedTransactionDetail changedTransactionDetail = adjustExistingTransaction(loan, newTransactionDetail, - loanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, - scheduleGeneratorDTO, reversalTxnExternalId); + adjustExistingTransaction(loan, newTransactionDetail, loanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, + existingReversedTransactionIds, scheduleGeneratorDTO, reversalTxnExternalId); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { @@ -1468,19 +1412,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.loanTransactionRepository.saveAndFlush(newTransactionDetail); } - /* - * TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the - * time being, not a major issue for now as this loop is entered only in edge cases (when a adjustment is made - * before the latest payment recorded against the loan) - */ - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); final String noteText = command.stringValueOfParameterNamed("note"); @@ -1545,13 +1476,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .with(changes).build(); } - public ChangedTransactionDetail adjustExistingTransaction(final Loan loan, final LoanTransaction newTransactionDetail, + public void adjustExistingTransaction(final Loan loan, final LoanTransaction newTransactionDetail, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, final List existingTransactionIds, final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, final ExternalId reversalExternalId) { - - ChangedTransactionDetail changedTransactionDetail = null; - existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); @@ -1598,11 +1526,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.updateLoanSummaryAndStatus(); if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) { - changedTransactionDetail = loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, - newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment, scheduleGeneratorDTO); + loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail, + loanLifecycleStateMachine, transactionForAdjustment, scheduleGeneratorDTO); } - - return changedTransactionDetail; } @Transactional @@ -1763,8 +1689,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf LoanEvent.LOAN_REPAYMENT_OR_WAIVER); loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_REPAYMENT_OR_WAIVER, waiveInterestTransaction.getTransactionDate()); - final ChangedTransactionDetail changedTransactionDetail = waiveInterest(loan, waiveInterestTransaction, loanLifecycleStateMachine, - existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); + waiveInterest(loan, waiveInterestTransaction, loanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, + scheduleGeneratorDTO); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.reprocessExistingAccruals(loan); @@ -1772,21 +1698,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } this.loanTransactionRepository.saveAndFlush(waiveInterestTransaction); - - /*** - * TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the - * time being, not a major issue for now as this loop is entered only in edge cases (when a waiver is made - * before the latest payment recorded against the loan) - ***/ - - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); final String noteText = command.stringValueOfParameterNamed("note"); @@ -1874,45 +1785,41 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .withLoanId(loanId) // .with(changes); - final ChangedTransactionDetail changedTransactionDetail = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes, + final Optional loanTransactionOptional = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes, existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO); - if (changedTransactionDetail != null) { + if (loanTransactionOptional.isPresent()) { + final LoanTransaction loanTransaction = loanTransactionOptional.get(); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); } - LoanTransaction writeOff = changedTransactionDetail.getNewTransactionMappings().remove(0L); - this.loanTransactionRepository.saveAndFlush(writeOff); - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - this.loanTransactionRepository.save(mapEntry.getValue()); - this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } + this.loanTransactionRepository.saveAndFlush(loanTransaction); saveLoanWithDataIntegrityViolationChecks(loan); final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { changes.put("note", noteText); - final Note note = Note.loanTransactionNote(loan, writeOff, noteText); + final Note note = Note.loanTransactionNote(loan, loanTransaction, noteText); this.noteRepository.save(note); } loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(), false); loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); - businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff)); + businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(loanTransaction)); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); - builder.withEntityId(writeOff.getId()).withEntityExternalId(writeOff.getExternalId()); + builder.withEntityId(loanTransaction.getId()).withEntityExternalId(loanTransaction.getExternalId()); } + return builder.build(); } @Transactional @Override public CommandProcessingResult closeLoan(final Long loanId, final JsonCommand command) { - this.loanTransactionValidator.validateTransactionWithNoAmount(command.json()); final Loan loan = this.loanAssembler.assembleFrom(loanId); @@ -1946,22 +1853,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LocalDate closureDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE); loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_CLOSED); loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.REPAID_IN_FULL, closureDate); - ChangedTransactionDetail changedTransactionDetail = close(loan, command, loanLifecycleStateMachine, changes, existingTransactionIds, - existingReversedTransactionIds, scheduleGeneratorDTO); + final Optional loanTransactionOptional = close(loan, command, loanLifecycleStateMachine, changes, + existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); } - final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L); - if (possibleClosingTransaction != null) { - this.loanTransactionRepository.saveAndFlush(possibleClosingTransaction); - } - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - this.loanTransactionRepository.save(mapEntry.getValue()); - this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } + loanTransactionOptional.ifPresent(this.loanTransactionRepository::saveAndFlush); saveLoanWithDataIntegrityViolationChecks(loan); final String noteText = command.stringValueOfParameterNamed("note"); @@ -1989,12 +1889,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds); CommandProcessingResult result; - if (possibleClosingTransaction != null) { - + if (loanTransactionOptional.isPresent()) { result = new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // - .withEntityId(possibleClosingTransaction.getId()) // - .withEntityExternalId(possibleClosingTransaction.getExternalId()) // + .withEntityId(loanTransactionOptional.get().getId()) // + .withEntityExternalId(loanTransactionOptional.get().getExternalId()) // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) // .withGroupId(loan.getGroupId()) // @@ -2589,20 +2488,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction)); loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.WRITE_OFF_OUTSTANDING_UNDO); - ChangedTransactionDetail changedTransactionDetail = undoWrittenOff(loan, loanLifecycleStateMachine, existingTransactionIds, - existingReversedTransactionIds); + undoWrittenOff(loan, loanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); } - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(), @@ -2730,20 +2620,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf LocalDate recalculateFrom = null; ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); - ChangedTransactionDetail changedTransactionDetail = null; - if (command.entityId() != null) { loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE); - changedTransactionDetail = updateDisbursementDateAndAmountForTranche(loan, loanDisbursementDetails, command, changes, - scheduleGeneratorDTO); + updateDisbursementDateAndAmountForTranche(loan, loanDisbursementDetails, command, changes, scheduleGeneratorDTO); } else { loan.repaymentScheduleDetail().setPrincipal(loan.getPrincipalAmountForRepaymentSchedule()); - if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else { + } else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); - loan.processPostDisbursementTransactions(); + reprocessLoanTransactionsService.processPostDisbursementTransactions(loan); } } @@ -2752,14 +2639,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); } - if (command.entityId() != null && changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { createLoanScheduleArchive(loan, scheduleGeneratorDTO); @@ -2817,22 +2696,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); - ChangedTransactionDetail changedTransactionDetail = loanScheduleService.recalculateScheduleFromLastTransaction(loan, generatorDTO, - existingTransactionIds, existingReversedTransactionIds); + loanScheduleService.recalculateScheduleFromLastTransaction(loan, generatorDTO, existingTransactionIds, + existingReversedTransactionIds); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); } - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(), false); @@ -2909,9 +2780,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (loan.isActualDisbursedOnDateEarlierOrLaterThanExpected(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged || rescheduledRepaymentDate != null) { - if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else { + } else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } } @@ -3168,7 +3039,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override @Transactional public CommandProcessingResult chargeOff(JsonCommand command) { - loanTransactionValidator.validateChargeOffTransaction(command.json()); final Map changes = new LinkedHashMap<>(); @@ -3225,18 +3095,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { - final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null); - loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - loan.addLoanTransaction(chargeOffTransaction); - ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - // Trigger transaction replayed event - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); + if (loan.isCumulativeSchedule()) { + final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null); + loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); } + loan.addLoanTransaction(chargeOffTransaction); + reprocessLoanTransactionsService.reprocessTransactions(loan); } else { loan.addLoanTransaction(chargeOffTransaction); loan.getTransactionProcessor().processLatestTransaction(chargeOffTransaction, @@ -3307,30 +3171,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.liftChargeOff(); loanTransactionRepository.saveAndFlush(chargedOffTransaction); - if (loan.isProgressiveSchedule()) { - final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null); - if (loan.isInterestBearingAndInterestRecalculationEnabled()) { - loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else { - loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); - } - - ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); - if (changedTransactionDetail != null) { - for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { - loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); - accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); - } - replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); - } + final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null); + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { + loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); + } else if (loan.isProgressiveSchedule()) { + loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } + + reprocessLoanTransactionsService.reprocessTransactions(loan); + saveLoanWithDataIntegrityViolationChecks(loan); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoChargeOffBusinessEvent(chargedOffTransaction)); - loan.reprocessTransactions(); - return new CommandProcessingResultBuilder() // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) // @@ -3577,12 +3431,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.getLoanTransactions().retainAll(retainTransactions); } - private ChangedTransactionDetail closeAsWrittenOff(final Loan loan, final JsonCommand command, + private Optional closeAsWrittenOff(final Loan loan, final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor(); - ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loan, scheduleGeneratorDTO); + closeDisbursements(loan, scheduleGeneratorDTO); final LocalDate writtenOffOnLocalDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE); loan.setClosedOnDate(writtenOffOnLocalDate); @@ -3590,82 +3444,74 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.setClosedBy(currentUser); final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.WRITE_OFF_OUTSTANDING, loan); - LoanTransaction loanTransaction = null; - if (!statusEnum.hasStateOf(loan.getStatus())) { - loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan); - changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus())); - - existingTransactionIds.addAll(loan.findExistingTransactionIds()); - existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); - - final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(EXTERNAL_ID); - - ExternalId externalId = ExternalIdFactory.produce(txnExternalId); - - if (externalId.isEmpty() && TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) { - externalId = ExternalId.generate(); - } - - changes.put(CLOSED_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); - changes.put(WRITTEN_OFF_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); - changes.put("externalId", externalId); - - if (DateUtils.isBefore(writtenOffOnLocalDate, loan.getDisbursementDate())) { - final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: " - + loan.getDisbursementDate().toString(); - throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.before.submittal.date", errorMessage, - writtenOffOnLocalDate, loan.getDisbursementDate()); - } - - if (DateUtils.isDateInTheFuture(writtenOffOnLocalDate)) { - final String errorMessage = "The date on which a loan is written off cannot be in the future."; - throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate); - } - - loanTransaction = LoanTransaction.writeoff(loan, loan.getOffice(), writtenOffOnLocalDate, externalId); - LocalDate lastTransactionDate = loan.getLastUserTransactionDate(); - if (DateUtils.isAfter(lastTransactionDate, writtenOffOnLocalDate)) { - final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions."; - throw new InvalidLoanStateTransitionException("writeoff", "must.occur.on.or.after.other.transaction.dates", errorMessage, - writtenOffOnLocalDate); - } - - loan.addLoanTransaction(loanTransaction); - loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, - new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); - - loan.updateLoanSummaryDerivedFields(); + if (statusEnum.hasStateOf(loan.getStatus())) { + return Optional.empty(); } - if (changedTransactionDetail == null) { - changedTransactionDetail = new ChangedTransactionDetail(); + + loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan); + changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus())); + + existingTransactionIds.addAll(loan.findExistingTransactionIds()); + existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); + + final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(EXTERNAL_ID); + + ExternalId externalId = ExternalIdFactory.produce(txnExternalId); + + if (externalId.isEmpty() && TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) { + externalId = ExternalId.generate(); } - changedTransactionDetail.getNewTransactionMappings().put(0L, loanTransaction); - return changedTransactionDetail; + + changes.put(CLOSED_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); + changes.put(WRITTEN_OFF_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); + changes.put("externalId", externalId); + + if (DateUtils.isBefore(writtenOffOnLocalDate, loan.getDisbursementDate())) { + final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: " + + loan.getDisbursementDate().toString(); + throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.before.submittal.date", errorMessage, + writtenOffOnLocalDate, loan.getDisbursementDate()); + } + + if (DateUtils.isDateInTheFuture(writtenOffOnLocalDate)) { + final String errorMessage = "The date on which a loan is written off cannot be in the future."; + throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate); + } + + final LoanTransaction loanTransaction = LoanTransaction.writeoff(loan, loan.getOffice(), writtenOffOnLocalDate, externalId); + LocalDate lastTransactionDate = loan.getLastUserTransactionDate(); + if (DateUtils.isAfter(lastTransactionDate, writtenOffOnLocalDate)) { + final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions."; + throw new InvalidLoanStateTransitionException("writeoff", "must.occur.on.or.after.other.transaction.dates", errorMessage, + writtenOffOnLocalDate); + } + + loan.addLoanTransaction(loanTransaction); + loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loan.getCurrency(), + loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + + loan.updateLoanSummaryDerivedFields(); + + return Optional.of(loanTransaction); } - private ChangedTransactionDetail closeDisbursements(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO) { - ChangedTransactionDetail changedTransactionDetail = null; + private void closeDisbursements(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO) { if (loan.isDisbursementAllowed() && loan.atLeastOnceDisbursed()) { loan.getLoanRepaymentScheduleDetail().setPrincipal(loan.getDisbursedAmount()); loan.removeDisbursementDetail(); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); - if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } - if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + } else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - changedTransactionDetail = loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); LocalDate lastLoanTransactionDate = loan.getLatestTransactionDate(); loan.doPostLoanTransactionChecks(lastLoanTransactionDate, loanLifecycleStateMachine); } - return changedTransactionDetail; } - private ChangedTransactionDetail close(final Loan loan, final JsonCommand command, + private Optional close(final Loan loan, final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { @@ -3695,7 +3541,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate); } final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor(); - ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loan, scheduleGeneratorDTO); + closeDisbursements(loan, scheduleGeneratorDTO); LoanTransaction loanTransaction = null; if (loan.isOpen()) { @@ -3747,16 +3593,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } - if (changedTransactionDetail == null) { - changedTransactionDetail = new ChangedTransactionDetail(); - } - changedTransactionDetail.getNewTransactionMappings().put(0L, loanTransaction); - return changedTransactionDetail; + return Optional.ofNullable(loanTransaction); } - private ChangedTransactionDetail updateDisbursementDateAndAmountForTranche(final Loan loan, - final LoanDisbursementDetails disbursementDetails, final JsonCommand command, final Map actualChanges, - final ScheduleGeneratorDTO scheduleGeneratorDTO) { + private void updateDisbursementDateAndAmountForTranche(final Loan loan, final LoanDisbursementDetails disbursementDetails, + final JsonCommand command, final Map actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO) { final Locale locale = command.extractLocale(); final BigDecimal principal = command.bigDecimalValueOfParameterNamed(LoanApiConstants.updatedDisbursementPrincipalParameterName, locale); @@ -3772,15 +3613,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.getLoanRepaymentScheduleDetail().setPrincipal(loan.getPrincipalAmountForRepaymentSchedule()); - if (loan.isInterestBearingAndInterestRecalculationEnabled() && !(loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff))) { + if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); - } else { + } else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - return loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); } private Map undoLastDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List existingTransactionIds, @@ -3826,17 +3665,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return actualChanges; } - private ChangedTransactionDetail waiveInterest(final Loan loan, final LoanTransaction waiveInterestTransaction, + private void waiveInterest(final Loan loan, final LoanTransaction waiveInterestTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); - return loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, waiveInterestTransaction, + loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, waiveInterestTransaction, loanLifecycleStateMachine, null, scheduleGeneratorDTO); } - private ChangedTransactionDetail undoWrittenOff(final Loan loan, final LoanLifecycleStateMachine loanLifecycleStateMachine, + private void undoWrittenOff(final Loan loan, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, final List existingReversedTransactionIds) { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); @@ -3845,13 +3684,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf "reversed"); writeOffTransaction.reverse(); loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, loan); - if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - return loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); } /** diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java index c72b44574..949e3619a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java @@ -41,7 +41,6 @@ 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.impl.AdvancedPaymentScheduleTransactionProcessor; -import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.starter.AdvancedPaymentScheduleTransactionProcessorCondition; import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; @@ -85,9 +84,7 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS List installmentsToReprocess = new ArrayList<>( loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList()); - if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java index 75eb32e72..dd8071ffa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingServiceImpl.java @@ -47,16 +47,15 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter; -import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository; 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.LoanTransactionNotFoundException; -import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; +import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.note.domain.Note; import org.apache.fineract.portfolio.note.domain.NoteRepository; import org.springframework.stereotype.Service; @@ -72,12 +71,12 @@ public class LoanReAgingServiceImpl { private final ExternalIdFactory externalIdFactory; private final BusinessEventNotifierService businessEventNotifierService; private final LoanTransactionRepository loanTransactionRepository; - private final LoanReAgingParameterRepository reAgingParameterRepository; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final NoteRepository noteRepository; private final LoanChargeValidator loanChargeValidator; private final LoanUtilService loanUtilService; private final LoanScheduleService loanScheduleService; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; public CommandProcessingResult reAge(Long loanId, JsonCommand command) { Loan loan = loanAssembler.assembleFrom(loanId); @@ -129,13 +128,11 @@ public class LoanReAgingServiceImpl { } reverseReAgeTransaction(reAgeTransaction, command); loanTransactionRepository.saveAndFlush(reAgeTransaction); - if (loan.getLoanProductRelatedDetail() != null - && loan.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE) - && loan.getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff)) { + if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } - loan.reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(loan); loan.updateLoanScheduleDependentDerivedFields(); persistNote(loan, command, changes); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java index d2fee2427..ef85d3f1a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationServiceImpl.java @@ -49,6 +49,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Mon import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; +import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -64,6 +65,7 @@ public class LoanReAmortizationServiceImpl { private final LoanTransactionRepository loanTransactionRepository; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanChargeValidator loanChargeValidator; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; public CommandProcessingResult reAmortize(Long loanId, JsonCommand command) { Loan loan = loanAssembler.assembleFrom(loanId); @@ -132,7 +134,7 @@ public class LoanReAmortizationServiceImpl { "reversed"); reAmortizeTransaction.reverse(reversalExternalId); reAmortizeTransaction.manuallyAdjustedOrReversed(); - reAmortizeTransaction.getLoan().reprocessTransactions(); + reprocessLoanTransactionsService.reprocessTransactions(reAmortizeTransaction.getLoan()); } private LoanTransaction findLatestNonReversedReAmortizeTransaction(Loan loan) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java index d8c12f781..dcf9cdb64 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java @@ -42,7 +42,6 @@ import org.apache.fineract.organisation.teller.data.CashierTransactionDataValida import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository; import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository; -import org.apache.fineract.portfolio.account.domain.AccountTransferRepository; import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountNumberGenerator; import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; @@ -64,13 +63,13 @@ import org.apache.fineract.portfolio.fund.domain.FundRepository; import org.apache.fineract.portfolio.fund.service.FundReadPlatformService; import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; import org.apache.fineract.portfolio.group.service.GroupReadPlatformService; -import org.apache.fineract.portfolio.interestpauses.service.AccountTransfersService; import org.apache.fineract.portfolio.interestpauses.service.InterestPauseReadPlatformService; import org.apache.fineract.portfolio.interestpauses.service.InterestPauseReadPlatformServiceImpl; import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformService; import org.apache.fineract.portfolio.interestpauses.service.InterestPauseWritePlatformServiceImpl; import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; @@ -104,6 +103,7 @@ import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlat import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService; import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.LoanAccountServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualEventService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService; @@ -141,6 +141,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformServic import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformServiceJpaRepositoryImpl; import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService; import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventServiceImpl; +import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; @@ -192,13 +193,12 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(LoanApplicationWritePlatformService.class) public LoanApplicationWritePlatformService loanApplicationWritePlatformService(PlatformSecurityContext context, - FromJsonHelper fromJsonHelper, LoanApplicationTransitionValidator loanApplicationTransitionValidator, - LoanApplicationValidator loanApplicationValidator, LoanRepositoryWrapper loanRepositoryWrapper, NoteRepository noteRepository, - LoanAssembler loanAssembler, LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + LoanApplicationTransitionValidator loanApplicationTransitionValidator, LoanApplicationValidator loanApplicationValidator, + LoanRepositoryWrapper loanRepositoryWrapper, NoteRepository noteRepository, LoanAssembler loanAssembler, + LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, CalendarRepository calendarRepository, CalendarInstanceRepository calendarInstanceRepository, SavingsAccountRepositoryWrapper savingsAccountRepository, AccountAssociationsRepository accountAssociationsRepository, - LoanReadPlatformService loanReadPlatformService, BusinessEventNotifierService businessEventNotifierService, - ConfigurationDomainService configurationDomainService, LoanScheduleAssembler loanScheduleAssembler, + BusinessEventNotifierService businessEventNotifierService, LoanScheduleAssembler loanScheduleAssembler, LoanUtilService loanUtilService, CalendarReadPlatformService calendarReadPlatformService, EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, GLIMAccountInfoRepository glimRepository, LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService, @@ -291,20 +291,20 @@ public class LoanAccountConfiguration { ConfigurationDomainService configurationDomainService, LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, ExternalIdFactory externalIdFactory, AccountTransferDetailRepository accountTransferDetailRepository, - LoanChargeAssembler loanChargeAssembler, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService, - PaymentDetailWritePlatformService paymentDetailWritePlatformService, NoteRepository noteRepository, - LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, + LoanChargeAssembler loanChargeAssembler, PaymentDetailWritePlatformService paymentDetailWritePlatformService, + NoteRepository noteRepository, LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, LoanAccrualsProcessingService loanAccrualsProcessingService, LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanChargeValidator loanChargeValidator, - LoanScheduleService loanScheduleService) { + LoanScheduleService loanScheduleService, ReprocessLoanTransactionsService reprocessLoanTransactionsService, + LoanAccountService loanAccountService) { return new LoanChargeWritePlatformServiceImpl(loanChargeApiJsonValidator, loanAssembler, chargeRepository, businessEventNotifierService, loanTransactionRepository, accountTransfersWritePlatformService, loanRepositoryWrapper, journalEntryWritePlatformService, loanAccountDomainService, loanChargeRepository, loanWritePlatformService, loanUtilService, loanChargeReadPlatformService, defaultLoanLifecycleStateMachine, accountAssociationsReadPlatformService, fromApiJsonHelper, configurationDomainService, loanRepaymentScheduleTransactionProcessorFactory, externalIdFactory, - accountTransferDetailRepository, loanChargeAssembler, replayedTransactionBusinessEventService, - paymentDetailWritePlatformService, noteRepository, loanAccrualTransactionBusinessEventService, - loanAccrualsProcessingService, loanDownPaymentTransactionValidator, loanChargeValidator, loanScheduleService); + accountTransferDetailRepository, loanChargeAssembler, paymentDetailWritePlatformService, noteRepository, + loanAccrualTransactionBusinessEventService, loanAccrualsProcessingService, loanDownPaymentTransactionValidator, + loanChargeValidator, loanScheduleService, reprocessLoanTransactionsService, loanAccountService); } @Bean @@ -386,13 +386,13 @@ public class LoanAccountConfiguration { PostDatedChecksRepository postDatedChecksRepository, LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository, LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService, - ExternalIdFactory externalIdFactory, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService, - LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, ErrorHandler errorHandler, - LoanDownPaymentHandlerService loanDownPaymentHandlerService, AccountTransferRepository accountTransferRepository, + ExternalIdFactory externalIdFactory, LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, + ErrorHandler errorHandler, LoanDownPaymentHandlerService loanDownPaymentHandlerService, LoanTransactionAssembler loanTransactionAssembler, LoanAccrualsProcessingService loanAccrualsProcessingService, LoanOfficerValidator loanOfficerValidator, LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanDisbursementService loanDisbursementService, LoanScheduleService loanScheduleService, - LoanChargeValidator loanChargeValidator, LoanOfficerService loanOfficerService) { + LoanChargeValidator loanChargeValidator, LoanOfficerService loanOfficerService, + ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanAccountService loanAccountService) { return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer, loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository, loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository, @@ -403,10 +403,10 @@ public class LoanAccountConfiguration { guarantorDomainService, loanUtilService, entityDatatableChecksWritePlatformService, transactionProcessingStrategy, codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, defaultLoanLifecycleStateMachine, - loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService, - loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, accountTransferRepository, - loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator, - loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService); + loanAccountLockService, externalIdFactory, loanAccrualTransactionBusinessEventService, errorHandler, + loanDownPaymentHandlerService, loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, + loanDownPaymentTransactionValidator, loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService, + reprocessLoanTransactionsService, loanAccountService); } @Bean @@ -421,9 +421,11 @@ public class LoanAccountConfiguration { public LoanDownPaymentHandlerService loanDownPaymentHandlerService(LoanTransactionRepository loanTransactionRepository, BusinessEventNotifierService businessEventNotifierService, LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanScheduleService loanScheduleService, - LoanRefundService loanRefundService, LoanRefundValidator loanRefundValidator) { + LoanRefundService loanRefundService, LoanRefundValidator loanRefundValidator, + ReprocessLoanTransactionsService reprocessLoanTransactionsService) { return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService, - loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator); + loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator, + reprocessLoanTransactionsService); } @Bean @@ -435,8 +437,8 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(LoanDisbursementService.class) public LoanDisbursementService loanDisbursementService(LoanChargeValidator loanChargeValidator, - LoanDisbursementValidator loanDisbursementValidator) { - return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator); + LoanDisbursementValidator loanDisbursementValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService) { + return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, reprocessLoanTransactionsService); } @Bean @@ -447,8 +449,9 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(LoanScheduleService.class) - public LoanScheduleService loanScheduleService(LoanChargeService loanChargeService) { - return new LoanScheduleService(loanChargeService); + public LoanScheduleService loanScheduleService(LoanChargeService loanChargeService, + ReprocessLoanTransactionsService reprocessLoanTransactionsService) { + return new LoanScheduleService(loanChargeService, reprocessLoanTransactionsService); } @Bean @@ -472,10 +475,17 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(InterestPauseWritePlatformService.class) public InterestPauseWritePlatformService interestPauseWritePlatformService(LoanTermVariationsRepository loanTermVariationsRepository, - LoanRepositoryWrapper loanRepositoryWrapper, LoanAssembler loanAssembler, LoanAccountDomainService loanAccountDomainService, - AccountTransfersService accountTransfersService, - ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService) { + LoanRepositoryWrapper loanRepositoryWrapper, LoanAssembler loanAssembler, + ReprocessLoanTransactionsService reprocessLoanTransactionsService) { return new InterestPauseWritePlatformServiceImpl(loanTermVariationsRepository, loanRepositoryWrapper, loanAssembler, - loanAccountDomainService, accountTransfersService, replayedTransactionBusinessEventService); + reprocessLoanTransactionsService); } + + @Bean + @ConditionalOnMissingBean(LoanAccountService.class) + public LoanAccountService loanAccountService(LoanRepositoryWrapper loanRepositoryWrapper, + LoanTransactionRepository loanTransactionRepository) { + return new LoanAccountServiceImpl(loanRepositoryWrapper, loanTransactionRepository); + } + } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java index 17f633d3e..121cc61a5 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java @@ -40,6 +40,7 @@ import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; @@ -131,6 +132,12 @@ class LoanChargeWritePlatformServiceImplTest { @Mock private LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; + @Mock + private ReprocessLoanTransactionsService reprocessLoanTransactionsService; + + @Mock + private LoanAccountService loanAccountService; + @BeforeEach void setUp() { when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan); @@ -143,7 +150,7 @@ class LoanChargeWritePlatformServiceImplTest { when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); when(loanChargeRepository.saveAndFlush(any(LoanCharge.class))).thenReturn(loanCharge); when(loan.getCurrency()).thenReturn(monetaryCurrency); - when(loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(any())).thenReturn(loan); + when(loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(any())).thenReturn(loan); } @ParameterizedTest diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java index 47db3cde1..ade7b28df 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java @@ -101,12 +101,16 @@ public class LoanDownPaymentHandlerServiceImplTest { @Mock private LoanRefundValidator loanRefundValidator; + @Mock + private ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private LoanDownPaymentHandlerServiceImpl underTest; @BeforeEach public void setUp() { underTest = new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService, - loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator); + loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator, + reprocessLoanTransactionsService); moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP)); moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP); tempConfigServiceMock.when(TemporaryConfigurationServiceContainer::isExternalIdAutoGenerationEnabled).thenReturn(true);