FINERACT-2081: Refactoring for reprocess transactions and regenerate repayment schedule invocations

This commit is contained in:
Oleksii Novikov
2025-02-18 20:10:25 +01:00
committed by Adam Saghy
parent 6bb68051ce
commit b1384b9cce
28 changed files with 600 additions and 694 deletions
@@ -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<Long, LoanTransaction> 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();
}
@@ -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);
@@ -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<Long> {
return lastRepaymentDate;
}
public void removeLoanCharge(final LoanCharge loanCharge) {
public Optional<ChangedTransactionDetail> removeLoanCharge(final LoanCharge loanCharge) {
final boolean removed = loanCharge.isActive();
if (removed) {
loanCharge.setActive(false);
@@ -848,10 +849,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
* 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<Long> {
.anyMatch(loanChargePaidBy -> loanChargePaidBy.getLoanCharge().equals(loanCharge));
}
public Map<String, Object> updateLoanCharge(final LoanCharge loanCharge, final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(3);
if (getActiveCharges().contains(loanCharge)) {
final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
final Map<String, Object> 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<Long> {
return getStatus().isClosedWrittenOff();
}
public boolean isClosedWithOutstandingAmountMarkedForReschedule() {
return getStatus().isClosedWithOutsandingAmountMarkedForReschedule();
}
public boolean isCancelled() {
return isRejected() || isWithdrawn();
}
@@ -2696,20 +2664,25 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
/*
* Probably this is buggy when a chargeback transaction happens
*/
public void processPostDisbursementTransactions() {
public Optional<ChangedTransactionDetail> processPostDisbursementTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
final List<LoanTransaction> 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<Long> {
Money totalInterest = Money.zero(getCurrency());
Money feeCharges = Money.zero(getCurrency());
Money penaltyCharges = Money.zero(getCurrency());
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) {
totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(getCurrency()));
@@ -3106,11 +3078,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return this.createStandingInstructionAtDisbursement != null && this.createStandingInstructionAtDisbursement;
}
public List<LoanCharge> 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<Long> {
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<Long> {
}
return false;
}
public boolean hasChargeOffTransaction() {
return getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff);
}
}
@@ -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<String, Object> changes);
@@ -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);
}
@@ -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);
}
@@ -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,
@@ -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);
@@ -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<Long> existingTransactionIds, final List<Long> 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);
}
}
@@ -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);
}
@@ -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<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
loanAccountTransfersService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
}
}
@@ -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);
@@ -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();
}
@@ -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);
}
}
}
@@ -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<Long, LoanTransaction> 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<ApiParameterError> 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<Long, LoanTransaction> 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<Long, LoanTransaction> 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<Long> existingTransactionIds,
final List<Long> 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);
}
}
@@ -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<String, Object> changes = new HashMap<>();
List<LoanTermVariationsData> newVariationsData = new ArrayList<>();
List<LoanTermVariations> 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) //
@@ -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<Long, LoanTransaction> 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);
@@ -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<ApiParameterError> 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);
}
}
}
@@ -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);
@@ -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<Long, LoanTransaction> 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<String, Object> changes = loan.updateLoanCharge(loanCharge, command);
final Map<String, Object> changes = new LinkedHashMap<>(3);
if (loan.getActiveCharges().contains(loanCharge)) {
final BigDecimal amount = loan.calculateAmountPercentageAppliedTo(loanCharge);
final Map<String, Object> 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<Long, LoanTransaction> 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<String, Object> 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<Long> 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<Long, LoanTransaction> 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();
@@ -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<String, Object> actualChanges) {
final List<Long> 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));
}
}
@@ -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<Long, LoanTransaction> 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<String, Object> changes,
ScheduleGeneratorDTO scheduleGeneratorDTO) {
private void disburseLoan(JsonCommand command, boolean isPaymentTypeApplicableForDisbursementCharge, PaymentDetail paymentDetail,
Loan loan, AppUser currentUser, Map<String, Object> 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<LoanTransaction> 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> 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<Long, LoanTransaction> 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<LoanTransaction> 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<Long, LoanTransaction> 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<Long, LoanTransaction> 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<Long> existingTransactionIds, final List<Long> 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<Long, LoanTransaction> 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<LoanTransaction> 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<Long, LoanTransaction> 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<LoanTransaction> 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<Long, LoanTransaction> 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<Long, LoanTransaction> 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<Long, LoanTransaction> 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<Long, LoanTransaction> 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<String, Object> 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<Long, LoanTransaction> 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<Long, LoanTransaction> 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<LoanTransaction> closeAsWrittenOff(final Loan loan, final JsonCommand command,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map<String, Object> changes,
final List<Long> existingTransactionIds, final List<Long> 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<LoanTransaction> close(final Loan loan, final JsonCommand command,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map<String, Object> changes,
final List<Long> existingTransactionIds, final List<Long> 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<String, Object> actualChanges,
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
private void updateDisbursementDateAndAmountForTranche(final Loan loan, final LoanDisbursementDetails disbursementDetails,
final JsonCommand command, final Map<String, Object> 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<String, Object> undoLastDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> 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<Long> existingTransactionIds,
final List<Long> 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<Long> existingTransactionIds, final List<Long> 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);
}
/**
@@ -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<LoanRepaymentScheduleInstallment> 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);
}
@@ -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);
@@ -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) {
@@ -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);
}
}
@@ -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
@@ -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);