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);
}
}