FINERACT-2234: Improve accrual activity posting business step performance

This commit is contained in:
Oleksii Novikov
2025-06-10 13:16:36 +02:00
committed by Adam Saghy
parent 3ce44f836d
commit 4d7a3efc13
3 changed files with 128 additions and 57 deletions
@@ -29,6 +29,7 @@ import org.apache.fineract.portfolio.loanaccount.data.CumulativeIncomeFromIncome
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.data.TransactionPortionsForForeclosure; import org.apache.fineract.portfolio.loanaccount.data.TransactionPortionsForForeclosure;
import org.apache.fineract.portfolio.loanaccount.data.UnpaidChargeData; import org.apache.fineract.portfolio.loanaccount.data.UnpaidChargeData;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
@@ -188,6 +189,18 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
List<LoanTransaction> findNonReversedByLoanAndTypesAndAfterDate(@Param("loan") Loan loan, List<LoanTransaction> findNonReversedByLoanAndTypesAndAfterDate(@Param("loan") Loan loan,
@Param("types") Set<LoanTransactionType> types, @Param("transactionDate") LocalDate transactionDate); @Param("types") Set<LoanTransactionType> types, @Param("transactionDate") LocalDate transactionDate);
@Query("""
SELECT lt
FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND lt.typeOf = :type
AND lt.dateOf > :transactionDate
ORDER BY lt.dateOf
""")
List<LoanTransaction> findNonReversedByLoanAndTypeAndAfterDate(@Param("loan") Loan loan, @Param("type") LoanTransactionType type,
@Param("transactionDate") LocalDate transactionDate);
@Query(""" @Query("""
SELECT CASE WHEN COUNT(lt) > 0 THEN true ELSE false END SELECT CASE WHEN COUNT(lt) > 0 THEN true ELSE false END
FROM LoanTransaction lt FROM LoanTransaction lt
@@ -219,6 +232,15 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
""") """)
List<LoanTransaction> findNonReversedByLoanAndTypes(@Param("loan") Loan loan, @Param("types") Set<LoanTransactionType> types); List<LoanTransaction> findNonReversedByLoanAndTypes(@Param("loan") Loan loan, @Param("types") Set<LoanTransactionType> types);
@Query("""
SELECT lt
FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND lt.typeOf = :type
""")
List<LoanTransaction> findNonReversedByLoanAndType(@Param("loan") Loan loan, @Param("type") LoanTransactionType type);
@Query(""" @Query("""
SELECT lt SELECT lt
FROM LoanTransaction lt FROM LoanTransaction lt
@@ -303,6 +325,27 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
WHERE lt.loan = :loan WHERE lt.loan = :loan
AND lt.reversed = false AND lt.reversed = false
""") """)
List<LoanTransaction> findNonReversedTransactionsByLoan(@Param("loan") Loan loan); List<LoanTransaction> findNonReversedByLoan(@Param("loan") Loan loan);
@Query("""
SELECT lt FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND lt.typeOf = :type
AND lt.dateOf IN :transactionDates
ORDER BY lt.id
""")
List<LoanTransaction> findNonReversedLoanAndTypeAndDates(@Param("loan") Loan loan, @Param("type") LoanTransactionType type,
@Param("transactionDates") Set<LocalDate> transactionDates);
@Query("""
SELECT lt FROM LoanTransaction lt
WHERE lt.loan = :loan
AND lt.reversed = false
AND lt.typeOf = :type
ORDER BY lt.dateOf DESC
""")
List<LoanTransaction> findNonReversedByLoanAndType(@Param("loan") Loan loan, @Param("type") LoanTransactionType type,
Pageable pageable);
} }
@@ -77,7 +77,7 @@ public class LoanAccountingBridgeMapper {
final List<Long> existingReversedTransactionIds, final boolean isAccountTransfer, final Loan loan) { final List<Long> existingReversedTransactionIds, final boolean isAccountTransfer, final Loan loan) {
List<LoanTransaction> transactions; List<LoanTransaction> transactions;
if (existingTransactionIds == null || existingTransactionIds.isEmpty()) { if (existingTransactionIds == null || existingTransactionIds.isEmpty()) {
transactions = loanTransactionRepository.findNonReversedTransactionsByLoan(loan); transactions = loanTransactionRepository.findNonReversedByLoan(loan);
} else if (existingReversedTransactionIds == null || existingReversedTransactionIds.isEmpty()) { } else if (existingReversedTransactionIds == null || existingReversedTransactionIds.isEmpty()) {
transactions = loanTransactionRepository.findTransactionsForAccountingBridge(loan, existingTransactionIds); transactions = loanTransactionRepository.findTransactionsForAccountingBridge(loan, existingTransactionIds);
} else { } else {
@@ -18,12 +18,15 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.service; package org.apache.fineract.portfolio.loanaccount.service;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -40,7 +43,10 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.springframework.data.domain.PageRequest;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -56,40 +62,47 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
private final LoanTransactionAssembler loanTransactionAssembler; private final LoanTransactionAssembler loanTransactionAssembler;
private final LoanAccountService loanAccountService; private final LoanAccountService loanAccountService;
private final LoanBalanceService loanBalanceService; private final LoanBalanceService loanBalanceService;
private final LoanTransactionRepository loanTransactionRepository;
@Override @Override
@Transactional(propagation = Propagation.REQUIRES_NEW) @Transactional(propagation = Propagation.REQUIRES_NEW)
public void makeAccrualActivityTransaction(@NotNull Long loanId, @NotNull LocalDate currentDate) { public void makeAccrualActivityTransaction(final @NonNull Long loanId, final @NonNull LocalDate currentDate) {
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true); Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
makeAccrualActivityTransaction(loan, currentDate); makeAccrualActivityTransaction(loan, currentDate);
} }
@Override @Override
public void makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LocalDate currentDate) { public void makeAccrualActivityTransaction(final @NonNull Loan loan, final @NonNull LocalDate currentDate) {
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting() || !loan.isOpen()) { if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting() || !loan.isOpen()) {
return; return;
} }
// check if loan has installment in the past or due on current date // check if loan has installment in the past or due on current date
List<LoanRepaymentScheduleInstallment> installments = loan final List<LoanRepaymentScheduleInstallment> installments = loan
.getRepaymentScheduleInstallments(i -> !i.isDownPayment() && !DateUtils.isBefore(currentDate, i.getDueDate())); .getRepaymentScheduleInstallments(i -> !i.isDownPayment() && !DateUtils.isBefore(currentDate, i.getDueDate()));
for (LoanRepaymentScheduleInstallment installment : installments) {
LocalDate dueDate = installment.getDueDate(); if (installments.isEmpty()) {
// check if there is any not-replayed-accrual-activity related to business date return;
ArrayList<LoanTransaction> existingActivities = new ArrayList<>( }
loan.getLoanTransactions(t -> t.isNotReversed() && t.isAccrualActivity() && t.getTransactionDate().isEqual(dueDate)));
boolean hasExisting = !existingActivities.isEmpty(); final Map<LocalDate, List<LoanTransaction>> existingActivitiesByDate = loadExistingAccrualActivitiesByDate(loan, installments);
LoanTransaction existingActivity = hasExisting ? existingActivities.get(0) : null;
installments.forEach(installment -> {
final LocalDate dueDate = installment.getDueDate();
final List<LoanTransaction> existingActivities = existingActivitiesByDate.getOrDefault(dueDate, Collections.emptyList());
final boolean hasExisting = !existingActivities.isEmpty();
final LoanTransaction existingActivity = hasExisting ? existingActivities.getFirst() : null;
makeOrReplayActivity(loan, installment, existingActivity); makeOrReplayActivity(loan, installment, existingActivity);
if (hasExisting) { if (hasExisting) {
existingActivities.remove(existingActivity); existingActivities.remove(existingActivity);
existingActivities.forEach(this::reverseAccrualActivityTransaction); existingActivities.forEach(this::reverseAccrualActivityTransaction);
} }
} });
} }
@Override @Override
@Transactional @Transactional
public void processAccrualActivityForLoanClosure(@NotNull Loan loan) { public void processAccrualActivityForLoanClosure(final @NonNull Loan loan) {
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
return; return;
} }
@@ -97,7 +110,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
LocalDate closureDate = loanBalanceService.isOverPaid(loan) ? loan.getOverpaidOnDate() : loan.getClosedOnDate(); LocalDate closureDate = loanBalanceService.isOverPaid(loan) ? loan.getOverpaidOnDate() : loan.getClosedOnDate();
// Reverse accrual activities posted after the closure date // Reverse accrual activities posted after the closure date
loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed() && t.getDateOf().isAfter(closureDate)) loanTransactionRepository.findNonReversedByLoanAndTypeAndAfterDate(loan, LoanTransactionType.ACCRUAL_ACTIVITY, closureDate)
.forEach(this::reverseAccrualActivityTransaction); .forEach(this::reverseAccrualActivityTransaction);
BigDecimal feeChargesPortion = BigDecimal.ZERO; BigDecimal feeChargesPortion = BigDecimal.ZERO;
@@ -113,7 +126,8 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
} }
} }
List<LoanTransaction> accrualActivities = loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed()); List<LoanTransaction> accrualActivities = loanTransactionRepository.findNonReversedByLoanAndType(loan,
LoanTransactionType.ACCRUAL_ACTIVITY);
// Check each past installment for accrual activity // Check each past installment for accrual activity
for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) { for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
@@ -130,14 +144,14 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
// Reverse and recreate if inconsistent or duplicate // Reverse and recreate if inconsistent or duplicate
installmentAccruals.forEach(this::reverseAccrualActivityTransaction); installmentAccruals.forEach(this::reverseAccrualActivityTransaction);
makeAccrualActivityTransaction(loan, installment, installment.getDueDate()); makeAccrualActivityTransaction(loan, installment, installment.getDueDate());
} else if (!validateActivityTransaction(installment, installmentAccruals.get(0))) { } else if (!validateActivityTransaction(installment, installmentAccruals.getFirst())) {
reverseReplayAccrualActivityTransaction(loan, installmentAccruals.get(0), installment, installment.getDueDate()); reverseReplayAccrualActivityTransaction(loan, installmentAccruals.getFirst(), installment, installment.getDueDate());
} }
} }
} }
// Subtract already posted accrual activities // Subtract already posted accrual activities
accrualActivities = loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed()); accrualActivities = loanTransactionRepository.findNonReversedByLoanAndType(loan, LoanTransactionType.ACCRUAL_ACTIVITY);
for (LoanTransaction accrualActivity : accrualActivities) { for (LoanTransaction accrualActivity : accrualActivities) {
feeChargesPortion = MathUtil.subtract(feeChargesPortion, accrualActivity.getFeeChargesPortion()); feeChargesPortion = MathUtil.subtract(feeChargesPortion, accrualActivity.getFeeChargesPortion());
penaltyChargesPortion = MathUtil.subtract(penaltyChargesPortion, accrualActivity.getPenaltyChargesPortion()); penaltyChargesPortion = MathUtil.subtract(penaltyChargesPortion, accrualActivity.getPenaltyChargesPortion());
@@ -157,36 +171,50 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
@Override @Override
@Transactional @Transactional
public void processAccrualActivityForLoanReopen(@NotNull Loan loan) { public void processAccrualActivityForLoanReopen(final @NonNull Loan loan) {
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
return; return;
} }
// grab the latest AccrualActivityTransaction // grab the latest AccrualActivityTransaction
// it does not matter if it is on an installment due date or not because it was posted due to loan close // it does not matter if it is on an installment due date or not because it was posted due to loan close
LoanTransaction lastAccrualActivityMarkedToReverse = loan.getLoanTransactions().stream() final Optional<LoanTransaction> lastAccrualActivityMarkedToReverse = loanTransactionRepository
.filter(loanTransaction -> loanTransaction.isNotReversed() && loanTransaction.isAccrualActivity()) .findNonReversedByLoanAndType(loan, LoanTransactionType.ACCRUAL_ACTIVITY, PageRequest.of(0, 1)) //
.sorted(Comparator.comparing(LoanTransaction::getDateOf)).reduce((first, second) -> second).orElse(null); .stream().findFirst();
final LocalDate lastAccrualActivityTransactionDate = lastAccrualActivityMarkedToReverse == null ? null
: lastAccrualActivityMarkedToReverse.getDateOf(); final Optional<LocalDate> lastAccrualActivityTransactionDate = lastAccrualActivityMarkedToReverse.map(LoanTransaction::getDateOf);
LocalDate today = DateUtils.getBusinessLocalDate(); final LocalDate today = DateUtils.getBusinessLocalDate();
final List<LoanRepaymentScheduleInstallment> installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate = loan
.getRepaymentScheduleInstallments().stream() final List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments().stream().filter(installment -> {
.filter(installment -> installment.getDueDate().isBefore(today) boolean isDueBefore = installment.getDueDate().isBefore(today);
&& (DateUtils.isAfter(installment.getDueDate(), lastAccrualActivityTransactionDate) boolean isAfterOrEqualToLastAccrualDate = lastAccrualActivityTransactionDate
// if close event happened on installment due date .map(date -> DateUtils.isAfter(installment.getDueDate(), date)
// we should reverse replay it to calculate installment related accrual parts only // if close event happened on installment due date
|| installment.getDueDate().isEqual(lastAccrualActivityTransactionDate))) // we should reverse replay it to calculate installment related accrual parts only
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).toList(); || installment.getDueDate().isEqual(date))
for (LoanRepaymentScheduleInstallment installment : installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate) { .orElse(true);
makeOrReplayActivity(loan, installment, lastAccrualActivityMarkedToReverse); return isDueBefore && isAfterOrEqualToLastAccrualDate;
lastAccrualActivityMarkedToReverse = null; }).sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).toList();
}
if (lastAccrualActivityMarkedToReverse != null) { installments.forEach(installment -> {
reverseAccrualActivityTransaction(lastAccrualActivityMarkedToReverse); makeOrReplayActivity(loan, installment, lastAccrualActivityMarkedToReverse.orElse(null));
});
if (installments.isEmpty()) {
lastAccrualActivityMarkedToReverse.ifPresent(this::reverseAccrualActivityTransaction);
} }
} }
private void makeOrReplayActivity(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment, private Map<LocalDate, List<LoanTransaction>> loadExistingAccrualActivitiesByDate(final @NonNull Loan loan,
final List<LoanRepaymentScheduleInstallment> installments) {
final Set<LocalDate> dueDates = installments.stream().map(LoanRepaymentScheduleInstallment::getDueDate).collect(Collectors.toSet());
final List<LoanTransaction> allActivities = loanTransactionRepository.findNonReversedLoanAndTypeAndDates(loan,
LoanTransactionType.ACCRUAL_ACTIVITY, dueDates);
return allActivities.stream().collect(Collectors.groupingBy(LoanTransaction::getDateOf));
}
private void makeOrReplayActivity(final @NonNull Loan loan, final @NonNull LoanRepaymentScheduleInstallment installment,
LoanTransaction existingActivity) { LoanTransaction existingActivity) {
LocalDate dueDate = installment.getDueDate(); LocalDate dueDate = installment.getDueDate();
if (existingActivity == null) { if (existingActivity == null) {
@@ -196,10 +224,10 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
} }
} }
private LoanTransaction reverseReplayAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanTransaction loanTransaction, private void reverseReplayAccrualActivityTransaction(final @NonNull Loan loan, final @NonNull LoanTransaction loanTransaction,
@NotNull LoanRepaymentScheduleInstallment installment, @NotNull LocalDate transactionDate) { final @NonNull LoanRepaymentScheduleInstallment installment, final @NonNull LocalDate transactionDate) {
if (validateActivityTransaction(installment, loanTransaction)) { if (validateActivityTransaction(installment, loanTransaction)) {
return loanTransaction; return;
} }
LoanTransaction newLoanTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment, LoanTransaction newLoanTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
@@ -223,39 +251,39 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
} else { } else {
reverseAccrualActivityTransaction(loanTransaction); reverseAccrualActivityTransaction(loanTransaction);
} }
return newLoanTransaction;
} }
private boolean validateActivityTransaction(@NotNull LoanRepaymentScheduleInstallment installment, private boolean validateActivityTransaction(final @NonNull LoanRepaymentScheduleInstallment installment,
@NotNull LoanTransaction transaction) { final @NonNull LoanTransaction transaction) {
return DateUtils.isEqual(installment.getDueDate(), transaction.getDateOf()) return DateUtils.isEqual(installment.getDueDate(), transaction.getDateOf())
&& MathUtil.isEqualTo(transaction.getInterestPortion(), installment.getInterestCharged()) && MathUtil.isEqualTo(transaction.getInterestPortion(), installment.getInterestCharged())
&& MathUtil.isEqualTo(transaction.getFeeChargesPortion(), installment.getFeeChargesCharged()) && MathUtil.isEqualTo(transaction.getFeeChargesPortion(), installment.getFeeChargesCharged())
&& MathUtil.isEqualTo(transaction.getPenaltyChargesPortion(), installment.getPenaltyCharges()); && MathUtil.isEqualTo(transaction.getPenaltyChargesPortion(), installment.getPenaltyCharges());
} }
private void reverseAccrualActivityTransaction(LoanTransaction loanTransaction) { private void reverseAccrualActivityTransaction(final @NonNull LoanTransaction loanTransaction) {
loanTransaction.reverse(); loanTransaction.reverse();
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction); LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
} }
private LoanTransaction makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment, private void makeAccrualActivityTransaction(final @NonNull Loan loan, final @NonNull LoanRepaymentScheduleInstallment installment,
@NotNull LocalDate transactionDate) { final @NonNull LocalDate transactionDate) {
LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment, LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
transactionDate); transactionDate);
return newAccrualActivityTransaction == null ? null : makeAccrualActivityTransaction(loan, newAccrualActivityTransaction);
if (newAccrualActivityTransaction != null) {
makeAccrualActivityTransaction(loan, newAccrualActivityTransaction);
}
} }
private LoanTransaction makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanTransaction newAccrualActivityTransaction) { private void makeAccrualActivityTransaction(final @NonNull Loan loan, @NonNull LoanTransaction newAccrualActivityTransaction) {
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan));
newAccrualActivityTransaction = loanAccountService newAccrualActivityTransaction = loanAccountService
.saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction); .saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction);
loan.addLoanTransaction(newAccrualActivityTransaction);
businessEventNotifierService businessEventNotifierService
.notifyPostBusinessEvent(new LoanTransactionAccrualActivityPostBusinessEvent(newAccrualActivityTransaction)); .notifyPostBusinessEvent(new LoanTransactionAccrualActivityPostBusinessEvent(newAccrualActivityTransaction));
return newAccrualActivityTransaction;
} }
} }