FINERACT-2060: Progressive loan - final accrual calculation on closed loan
This commit is contained in:
committed by
Arnold Gálovics
parent
c4cdd6f51a
commit
136bb0009a
+21
-16
@@ -293,10 +293,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, isLoanToLoanTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
|
||||
setLoanDelinquencyTag(loan, transactionDate);
|
||||
|
||||
@@ -306,6 +304,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, isLoanToLoanTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
// disable all active standing orders linked to this loan if status
|
||||
// changes to closed
|
||||
disableStandingInstructionsLinkedToClosedLoan(loan);
|
||||
@@ -492,13 +494,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargePaymentPostBusinessEvent(newPaymentTransaction));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
return newPaymentTransaction;
|
||||
}
|
||||
|
||||
@@ -726,13 +728,15 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCreditBalanceRefundPostBusinessEvent(newCreditBalanceRefundTransaction));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return newCreditBalanceRefundTransaction;
|
||||
}
|
||||
|
||||
@@ -778,13 +782,14 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRefundPostBusinessEvent(newRefundTransaction));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, false);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
builderResult.withEntityId(newRefundTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId())
|
||||
.withGroupId(loan.getGroupId());
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public class AddPeriodicAccrualEntriesForLoansTasklet implements Tasklet {
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (Long loanId : loanIds) {
|
||||
try {
|
||||
loanAccrualsProcessingService.addIncomeAndAccrualTransactions(loanId);
|
||||
loanAccrualsProcessingService.addIncomePostingAndAccruals(loanId);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to add income and accrual transaction for loan {}", loanId, e);
|
||||
errors.add(e);
|
||||
|
||||
+4
-4
@@ -466,13 +466,13 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||
}
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
// update the loan object
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueAdjustScheduleBusinessEvent(loan));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan, true);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRescheduledDueAdjustScheduleBusinessEvent(loan));
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId)
|
||||
.withLoanId(loanRescheduleRequest.getLoan().getId()).with(changes).withClientId(loan.getClientId())
|
||||
.withOfficeId(loan.getOfficeId()).withGroupId(loan.getGroupId()).build();
|
||||
|
||||
+137
-84
@@ -18,24 +18,29 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent;
|
||||
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.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -46,131 +51,179 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActivityProcessingService {
|
||||
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
private final LoanWritePlatformService loanWritePlatformService;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanChargeValidator loanChargeValidator;
|
||||
private final LoanTransactionAssembler loanTransactionAssembler;
|
||||
private final LoanAccountDomainService loanAccountDomainService;
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void makeAccrualActivityTransaction(Long loanId, final LocalDate currentDate) {
|
||||
public void makeAccrualActivityTransaction(@NotNull Long loanId, @NotNull LocalDate currentDate) {
|
||||
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
makeAccrualActivityTransaction(loan, currentDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loan makeAccrualActivityTransaction(Loan loan, final LocalDate currentDate) {
|
||||
if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
// check if loan has installment due on business day
|
||||
Optional<LoanRepaymentScheduleInstallment> first = loan.getRepaymentScheduleInstallments().stream()
|
||||
.filter(loanRepaymentScheduleInstallment -> loanRepaymentScheduleInstallment.getDueDate().isEqual(currentDate))
|
||||
.findFirst();
|
||||
if (first.isPresent()) {
|
||||
final LoanRepaymentScheduleInstallment installment = first.get();
|
||||
// check if there is no not-replayed-accrual-activity related to business date
|
||||
List<LoanTransaction> loanTransactions = loan.getLoanTransactions(loanTransaction -> loanTransaction.isNotReversed()
|
||||
&& loanTransaction.isAccrualActivity() && loanTransaction.getTransactionDate().isEqual(currentDate));
|
||||
if (loanTransactions.isEmpty()) {
|
||||
loan = loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, currentDate);
|
||||
}
|
||||
public void makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LocalDate currentDate) {
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
return;
|
||||
}
|
||||
// check if loan has installment in the past or due on current date
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan
|
||||
.getRepaymentScheduleInstallments(i -> !i.isDownPayment() && !DateUtils.isBefore(currentDate, i.getDueDate()));
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
LocalDate dueDate = installment.getDueDate();
|
||||
// check if there is any not-replayed-accrual-activity related to business date
|
||||
ArrayList<LoanTransaction> existingActivities = new ArrayList<>(
|
||||
loan.getLoanTransactions(t -> t.isNotReversed() && t.isAccrualActivity() && t.getTransactionDate().isEqual(dueDate)));
|
||||
boolean hasExisting = !existingActivities.isEmpty();
|
||||
LoanTransaction existingActivity = hasExisting ? existingActivities.get(0) : null;
|
||||
makeOrReplayActivity(loan, installment, existingActivity);
|
||||
if (hasExisting) {
|
||||
existingActivities.remove(existingActivity);
|
||||
existingActivities.forEach(this::reverseAccrualActivityTransaction);
|
||||
}
|
||||
}
|
||||
return loan;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processAccrualActivityForLoanClosure(Loan loan) {
|
||||
public void processAccrualActivityForLoanClosure(@NotNull Loan loan) {
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
return;
|
||||
}
|
||||
LocalDate date = loan.isOverPaid() ? loan.getOverpaidOnDate() : loan.getClosedOnDate();
|
||||
List<LoanTransaction> accrualActivityTransaction = loan.getLoanTransactions().stream().filter(LoanTransaction::isNotReversed)
|
||||
.filter(LoanTransaction::isAccrualActivity).filter(loanTransaction -> loanTransaction.getDateOf().isAfter(date)).toList();
|
||||
if (!accrualActivityTransaction.isEmpty()) {
|
||||
accrualActivityTransaction.forEach(this::reverseAccrualActivityTransaction);
|
||||
}
|
||||
LoanTransaction loanTransaction = assembleClosingAccrualActivityTransaction(loan, date);
|
||||
if (loanTransaction.getAmount().compareTo(BigDecimal.ZERO) != 0) {
|
||||
loanWritePlatformService.makeAccrualActivityTransaction(loan, loanTransaction);
|
||||
}
|
||||
}
|
||||
// reverse after closure activities
|
||||
loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed() && t.getDateOf().isAfter(date))
|
||||
.forEach(this::reverseAccrualActivityTransaction);
|
||||
|
||||
private void reverseAccrualActivityTransaction(LoanTransaction loanTransaction) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction, "reversed");
|
||||
loanTransaction.reverse();
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
|
||||
private LoanTransaction assembleClosingAccrualActivityTransaction(Loan loan, LocalDate date) {
|
||||
// collect fees
|
||||
// calculate activity amounts
|
||||
BigDecimal feeChargesPortion = BigDecimal.ZERO;
|
||||
// collect penalties
|
||||
BigDecimal penaltyChargesPortion = BigDecimal.ZERO;
|
||||
// collect interests
|
||||
BigDecimal interestPortion = BigDecimal.ZERO;
|
||||
var currency = loan.getCurrency();
|
||||
// sum up all accruals
|
||||
// collect installment amounts
|
||||
for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
|
||||
feeChargesPortion = installment.getFeeAccrued(currency).getAmount().add(feeChargesPortion);
|
||||
penaltyChargesPortion = installment.getPenaltyAccrued(currency).getAmount().add(penaltyChargesPortion);
|
||||
interestPortion = installment.getInterestAccrued(currency).getAmount().add(interestPortion);
|
||||
feeChargesPortion = MathUtil.add(feeChargesPortion, installment.getFeeChargesCharged());
|
||||
penaltyChargesPortion = MathUtil.add(penaltyChargesPortion, installment.getPenaltyCharges());
|
||||
interestPortion = MathUtil.add(interestPortion, installment.getInterestCharged());
|
||||
}
|
||||
List<LoanTransaction> accrualActivities = loan.getLoanTransactions().stream().filter(LoanTransaction::isAccrualActivity)
|
||||
.filter(LoanTransaction::isNotReversed).toList();
|
||||
// subtract already Posted accruals
|
||||
// subtract already posted activities
|
||||
for (LoanTransaction accrualActivity : accrualActivities) {
|
||||
if (accrualActivity.getFeeChargesPortion() != null) {
|
||||
feeChargesPortion = feeChargesPortion.subtract(accrualActivity.getFeeChargesPortion());
|
||||
}
|
||||
if (accrualActivity.getPenaltyChargesPortion() != null) {
|
||||
penaltyChargesPortion = penaltyChargesPortion.subtract(accrualActivity.getPenaltyChargesPortion());
|
||||
}
|
||||
if (accrualActivity.getInterestPortion() != null) {
|
||||
interestPortion = interestPortion.subtract(accrualActivity.getInterestPortion());
|
||||
}
|
||||
feeChargesPortion = MathUtil.subtract(feeChargesPortion, accrualActivity.getFeeChargesPortion());
|
||||
penaltyChargesPortion = MathUtil.subtract(penaltyChargesPortion, accrualActivity.getPenaltyChargesPortion());
|
||||
interestPortion = MathUtil.subtract(interestPortion, accrualActivity.getInterestPortion());
|
||||
}
|
||||
|
||||
BigDecimal transactionAmount = feeChargesPortion.add(penaltyChargesPortion).add(interestPortion);
|
||||
ExternalId externalId = externalIdFactory.create();
|
||||
|
||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), date, transactionAmount, null,
|
||||
interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId);
|
||||
BigDecimal transactionAmount = MathUtil.add(feeChargesPortion, penaltyChargesPortion, interestPortion);
|
||||
if (!MathUtil.isGreaterThanZero(transactionAmount)) {
|
||||
return;
|
||||
}
|
||||
if (MathUtil.isLessThanZero(feeChargesPortion) || MathUtil.isLessThanZero(penaltyChargesPortion)
|
||||
|| MathUtil.isLessThanZero(interestPortion)) {
|
||||
// TODO reverse latest accrual activity if any amount is negative
|
||||
return;
|
||||
}
|
||||
LoanTransaction newActivity = new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), date,
|
||||
transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null,
|
||||
externalIdFactory.create());
|
||||
makeAccrualActivityTransaction(loan, newActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processAccrualActivityForLoanReopen(Loan loan) {
|
||||
LoanTransaction lastAccrualActivityMarkedToReverse = null;
|
||||
List<LoanTransaction> accrualActivityTransaction = loan.getLoanTransactions().stream()
|
||||
.filter(loanTransaction -> loanTransaction.isNotReversed() && loanTransaction.isAccrualActivity())
|
||||
.sorted(Comparator.comparing(LoanTransaction::getDateOf)).toList();
|
||||
public void processAccrualActivityForLoanReopen(@NotNull Loan loan) {
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
return;
|
||||
}
|
||||
// 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
|
||||
if (!accrualActivityTransaction.isEmpty()) {
|
||||
lastAccrualActivityMarkedToReverse = accrualActivityTransaction.get(accrualActivityTransaction.size() - 1);
|
||||
}
|
||||
LoanTransaction lastAccrualActivityMarkedToReverse = loan.getLoanTransactions().stream()
|
||||
.filter(loanTransaction -> loanTransaction.isNotReversed() && loanTransaction.isAccrualActivity())
|
||||
.sorted(Comparator.comparing(LoanTransaction::getDateOf)).reduce((first, second) -> second).orElse(null);
|
||||
final LocalDate lastAccrualActivityTransactionDate = lastAccrualActivityMarkedToReverse == null ? null
|
||||
: lastAccrualActivityMarkedToReverse.getDateOf();
|
||||
LocalDate today = DateUtils.getBusinessLocalDate();
|
||||
final List<LoanRepaymentScheduleInstallment> installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate = loan
|
||||
.getRepaymentScheduleInstallments().stream()
|
||||
.filter(installment -> installment.getDueDate().isBefore(today) && (lastAccrualActivityTransactionDate == null
|
||||
|| installment.getDueDate().isAfter(lastAccrualActivityTransactionDate)
|
||||
// if close event happened on installment due date
|
||||
// we should reverse replay it to calculate installment related accrual parts only
|
||||
|| installment.getDueDate().isEqual(lastAccrualActivityTransactionDate)))
|
||||
.filter(installment -> installment.getDueDate().isBefore(today)
|
||||
&& (DateUtils.isAfter(installment.getDueDate(), lastAccrualActivityTransactionDate)
|
||||
// if close event happened on installment due date
|
||||
// we should reverse replay it to calculate installment related accrual parts only
|
||||
|| installment.getDueDate().isEqual(lastAccrualActivityTransactionDate)))
|
||||
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).toList();
|
||||
for (LoanRepaymentScheduleInstallment installment : installmentsBetweenBusinessDateAndLastAccrualActivityTransactionDate) {
|
||||
if (lastAccrualActivityMarkedToReverse != null) {
|
||||
loanWritePlatformService.reverseReplayAccrualActivityTransaction(loan, lastAccrualActivityMarkedToReverse, installment,
|
||||
installment.getDueDate());
|
||||
lastAccrualActivityMarkedToReverse = null;
|
||||
} else {
|
||||
loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, installment.getDueDate());
|
||||
}
|
||||
makeOrReplayActivity(loan, installment, lastAccrualActivityMarkedToReverse);
|
||||
lastAccrualActivityMarkedToReverse = null;
|
||||
}
|
||||
if (lastAccrualActivityMarkedToReverse != null) {
|
||||
reverseAccrualActivityTransaction(lastAccrualActivityMarkedToReverse);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeOrReplayActivity(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment,
|
||||
LoanTransaction existingActivity) {
|
||||
LocalDate dueDate = installment.getDueDate();
|
||||
if (existingActivity == null) {
|
||||
makeAccrualActivityTransaction(loan, installment, dueDate);
|
||||
} else {
|
||||
reverseReplayAccrualActivityTransaction(loan, existingActivity, installment, dueDate);
|
||||
}
|
||||
}
|
||||
|
||||
private LoanTransaction reverseReplayAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanTransaction loanTransaction,
|
||||
@NotNull LoanRepaymentScheduleInstallment installment, @NotNull LocalDate transactionDate) {
|
||||
if (validateActivityTransaction(installment, loanTransaction)) {
|
||||
return loanTransaction;
|
||||
}
|
||||
|
||||
LoanTransaction newLoanTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
|
||||
transactionDate);
|
||||
if (newLoanTransaction != null) {
|
||||
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
||||
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction);
|
||||
loan.addLoanTransaction(newLoanTransaction);
|
||||
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||
data.setNewTransactionDetail(newLoanTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
reverseAccrualActivityTransaction(loanTransaction);
|
||||
return newLoanTransaction;
|
||||
}
|
||||
|
||||
private boolean validateActivityTransaction(@NotNull LoanRepaymentScheduleInstallment installment,
|
||||
@NotNull LoanTransaction transaction) {
|
||||
return DateUtils.isEqual(installment.getDueDate(), transaction.getDateOf())
|
||||
&& MathUtil.isEqualTo(transaction.getInterestPortion(), installment.getInterestCharged())
|
||||
&& MathUtil.isEqualTo(transaction.getFeeChargesPortion(), installment.getFeeChargesCharged())
|
||||
&& MathUtil.isEqualTo(transaction.getPenaltyChargesPortion(), installment.getPenaltyCharges());
|
||||
}
|
||||
|
||||
private void reverseAccrualActivityTransaction(LoanTransaction loanTransaction) {
|
||||
loanTransaction.reverse();
|
||||
loanTransaction.updateExternalId(null);
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
|
||||
private LoanTransaction makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment,
|
||||
@NotNull LocalDate transactionDate) {
|
||||
LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
|
||||
transactionDate);
|
||||
return newAccrualActivityTransaction == null ? null : makeAccrualActivityTransaction(loan, newAccrualActivityTransaction);
|
||||
}
|
||||
|
||||
private LoanTransaction makeAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanTransaction newAccrualActivityTransaction) {
|
||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan));
|
||||
newAccrualActivityTransaction = loanAccountDomainService
|
||||
.saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction);
|
||||
|
||||
loan.addLoanTransaction(newAccrualActivityTransaction);
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanTransactionAccrualActivityPostBusinessEvent(newAccrualActivityTransaction));
|
||||
return newAccrualActivityTransaction;
|
||||
}
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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 jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LoanAccrualEventService {
|
||||
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService;
|
||||
|
||||
@PostConstruct
|
||||
public void addListeners() {
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanCloseBusinessEvent.class, new LoanCloseListener());
|
||||
businessEventNotifierService.addPostBusinessEventListener(LoanBalanceChangedBusinessEvent.class, new LoanBalanceChangedListener());
|
||||
}
|
||||
|
||||
private final class LoanCloseListener implements BusinessEventListener<LoanCloseBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanCloseBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
LoanStatus status = loan.getStatus();
|
||||
if (status.isClosedObligationsMet() || status.isOverpaid()) {
|
||||
log.debug("Loan closure on accrual for loan {}", loan.getId());
|
||||
loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan);
|
||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoanBalanceChangedListener implements BusinessEventListener<LoanBalanceChangedBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanBalanceChangedBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
LoanStatus status = loan.getStatus();
|
||||
if (status.isClosedObligationsMet() || status.isOverpaid()) {
|
||||
log.debug("Loan balance change on accrual for loan {}", loan.getId());
|
||||
loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan);
|
||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
-118
@@ -56,14 +56,12 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccrualChargeData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccrualPeriodData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccrualPeriodsData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
|
||||
@@ -77,8 +75,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepayme
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -102,9 +98,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
||||
private final OfficeRepository officeRepository;
|
||||
private final LoanChargeRepository loanChargeRepository;
|
||||
private final LoanChargeValidator loanChargeValidator;
|
||||
|
||||
/**
|
||||
* method adds accrual for batch job "Add Periodic Accrual Transactions" and add accruals api for Loan
|
||||
@@ -167,9 +160,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
*/
|
||||
@Override
|
||||
public void reprocessExistingAccruals(@NotNull Loan loan) {
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
List<LoanTransaction> accrualTransactions = retrieveListOfAccrualTransactions(loan);
|
||||
if (!accrualTransactions.isEmpty()) {
|
||||
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
||||
@@ -185,7 +175,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void processAccrualsForInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled) {
|
||||
public void processAccrualsOnInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled, boolean addJournal) {
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
@@ -194,13 +184,31 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
return;
|
||||
}
|
||||
try {
|
||||
addPeriodicAccruals(accruedTill, loan);
|
||||
addAccruals(loan, accruedTill, true, false, addJournal);
|
||||
} catch (Exception e) {
|
||||
String globalisationMessageCode = "error.msg.accrual.exception";
|
||||
throw new GeneralPlatformDomainRuleException(globalisationMessageCode, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void addIncomePostingAndAccruals(Long loanId) throws LoanNotFoundException {
|
||||
if (loanId == null) {
|
||||
return;
|
||||
}
|
||||
Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
processIncomePostingAndAccruals(loan);
|
||||
this.loanRepositoryWrapper.saveAndFlush(loan);
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* method calculates accruals for loan with interest recalculation and compounding to be posted as income
|
||||
*/
|
||||
@@ -228,26 +236,8 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
}
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
LoanRepaymentScheduleInstallment lastInstallment = LoanRepaymentScheduleInstallment.getLastNonDownPaymentInstallment(installments);
|
||||
reverseTransactionsAfter(incomeTransactions, lastInstallment.getDueDate());
|
||||
reverseTransactionsAfter(accrualTransactions, lastInstallment.getDueDate());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void addIncomeAndAccrualTransactions(Long loanId) throws LoanNotFoundException {
|
||||
if (loanId == null) {
|
||||
return;
|
||||
}
|
||||
Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
|
||||
processIncomePostingAndAccruals(loan);
|
||||
this.loanRepositoryWrapper.saveAndFlush(loan);
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
reverseTransactionsAfter(incomeTransactions, lastInstallment.getDueDate(), false);
|
||||
reverseTransactionsAfter(accrualTransactions, lastInstallment.getDueDate(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,32 +254,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
processIncomeAndAccrualTransactionOnLoanClosure(loan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAccrualsOnLoanReopen(@NotNull Loan loan) {
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
List<LoanTransaction> accruals = retrieveListOfAccrualTransactions(loan);
|
||||
LocalDate accruedTill = loan.getAccruedTill();
|
||||
// if accruedTill is null, the all the accrual transactions will be reversed here
|
||||
if (reverseTransactionsAfter(accruals, accruedTill)) {
|
||||
return;
|
||||
}
|
||||
LoanTransaction closureAccrual = accruals.stream()
|
||||
.filter(t -> !t.isReversed() && t.isAccrual() && DateUtils.isEqual(t.getTransactionDate(), accruedTill))
|
||||
.sorted(LoanTransactionComparator.INSTANCE).reduce((first, second) -> second).orElse(null);
|
||||
if (closureAccrual == null) {
|
||||
return;
|
||||
}
|
||||
int firstInstallmentNumber = fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments());
|
||||
LoanRepaymentScheduleInstallment installment = loan.getRepaymentScheduleInstallment(
|
||||
i -> isInPeriod(closureAccrual.getDateOf(), i, i.getInstallmentNumber().equals(firstInstallmentNumber)));
|
||||
if (MathUtil.isGreaterThan(closureAccrual.getInterestPortion(), installment.getInterestCharged())
|
||||
|| MathUtil.isGreaterThan(closureAccrual.getFeeChargesPortion(), installment.getFeeChargesCharged())
|
||||
|| MathUtil.isGreaterThan(closureAccrual.getPenaltyChargesPortion(), installment.getPenaltyCharges())) {
|
||||
reverseAccrual(closureAccrual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method calculates accruals for loan on loan fore closure
|
||||
*/
|
||||
@@ -301,7 +265,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
&& (loan.getAccruedTill() == null || !DateUtils.isEqual(foreClosureDate, loan.getAccruedTill()))) {
|
||||
final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
|
||||
MonetaryCurrency currency = loan.getCurrency();
|
||||
reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate);
|
||||
reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate, false);
|
||||
|
||||
HashMap<String, Object> incomeDetails = new HashMap<>();
|
||||
|
||||
@@ -331,20 +295,29 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
return;
|
||||
}
|
||||
List<LoanTransaction> existingAccruals = retrieveListOfAccrualTransactions(loan);
|
||||
reverseTransactionsAfter(existingAccruals, loan.getLastLoanRepaymentScheduleInstallment().getDueDate());
|
||||
LocalDate lastDueDate = loan.getLastLoanRepaymentScheduleInstallment().getDueDate();
|
||||
reverseTransactionsAfter(existingAccruals, lastDueDate, addJournal);
|
||||
ensureAccrualTransactionMappings(loan);
|
||||
if (DateUtils.isAfter(tillDate, lastDueDate)) {
|
||||
tillDate = lastDueDate;
|
||||
}
|
||||
|
||||
boolean progressiveAccrual = isProgressiveAccrual(loan);
|
||||
LocalDate accruedTill = loan.getAccruedTill();
|
||||
if (progressiveAccrual && accruedTill != null && !DateUtils.isAfter(tillDate, accruedTill)
|
||||
&& existingAccruals.stream().anyMatch(t -> !t.isReversed() && !DateUtils.isBefore(t.getDateOf(), tillDate))) {
|
||||
return;
|
||||
LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||
LocalDate accrualDate = isFinal
|
||||
? (progressiveAccrual ? (DateUtils.isBefore(lastDueDate, businessDate) ? lastDueDate : businessDate)
|
||||
: getFinalAccrualTransactionDate(loan))
|
||||
: tillDate;
|
||||
if (progressiveAccrual && accruedTill != null && !DateUtils.isAfter(tillDate, accruedTill)) {
|
||||
if (isFinal) {
|
||||
reverseTransactionsAfter(existingAccruals, accrualDate, addJournal);
|
||||
} else if (existingAccruals.stream().anyMatch(t -> !t.isReversed() && !DateUtils.isBefore(t.getDateOf(), accrualDate))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AccrualPeriodsData accrualPeriods = calculateAccrualAmounts(loan, tillDate, periodic);
|
||||
|
||||
LocalDate accrualDate = isFinal ? (progressiveAccrual ? DateUtils.getBusinessLocalDate() : getFinalAccrualTransactionDate(loan))
|
||||
: tillDate;
|
||||
boolean mergeTransactions = isFinal || progressiveAccrual;
|
||||
MonetaryCurrency currency = loan.getLoanProductRelatedDetail().getCurrency();
|
||||
List<LoanTransaction> accrualTransactions = new ArrayList<>();
|
||||
@@ -399,17 +372,17 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFinal) {
|
||||
loan.setAccruedTill(tillDate);
|
||||
if (!isFinal || progressiveAccrual) {
|
||||
loan.setAccruedTill(isFinal ? accrualDate : tillDate);
|
||||
}
|
||||
ArrayList<Map<String, Object>> newTransactionMapping = new ArrayList<>();
|
||||
for (LoanTransaction accrualTransaction : accrualTransactions) {
|
||||
accrualTransaction = loanTransactionRepository.saveAndFlush(accrualTransaction);
|
||||
LoanTransactionBusinessEvent businessEvent = accrualTransaction.isAccrual()
|
||||
? new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction)
|
||||
: new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
|
||||
if (addJournal) {
|
||||
LoanTransactionBusinessEvent businessEvent = accrualTransaction.isAccrual()
|
||||
? new LoanAccrualTransactionCreatedBusinessEvent(accrualTransaction)
|
||||
: new LoanAccrualAdjustmentTransactionBusinessEvent(accrualTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(businessEvent);
|
||||
newTransactionMapping.add(accrualTransaction.toMapData(currency.getCode()));
|
||||
}
|
||||
}
|
||||
@@ -584,7 +557,9 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
|
||||
Money unrecognizedWaived = MathUtil.toMoney(calcChargeUnrecognizedWaivedAmount(paidBys, tillDate), currency);
|
||||
Money transactionWaived = MathUtil.minusToZero(waived, unrecognizedWaived);
|
||||
chargeData.setChargeAccrued(MathUtil.minusToZero(MathUtil.toMoney(calcChargeAccruedAmount(paidBys), currency), transactionWaived));
|
||||
Money transactionAccrued = MathUtil.toMoney(calcChargeAccruedAmount(paidBys), currency);
|
||||
chargeData.setTransactionAccrued(transactionAccrued);
|
||||
chargeData.setChargeAccrued(MathUtil.minusToZero(transactionAccrued, transactionWaived));
|
||||
|
||||
duePeriod.addCharge(chargeData);
|
||||
}
|
||||
@@ -720,17 +695,29 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
return;
|
||||
}
|
||||
ensureAccrualTransactionMappings(loan);
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
boolean isBasedOnSubmittedOnDate = !isChargeOnDueDate();
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
checkAndUpdateAccrualsForInstallment(loan, accrualTransactions, installments, isBasedOnSubmittedOnDate, installment);
|
||||
LoanRepaymentScheduleInstallment lastInstallment = loan.getLastLoanRepaymentScheduleInstallment();
|
||||
LocalDate lastDueDate = lastInstallment.getDueDate();
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
AccrualPeriodsData accrualPeriods = calculateAccrualAmounts(loan, lastDueDate, true);
|
||||
for (AccrualPeriodData period : accrualPeriods.getPeriods()) {
|
||||
Money interestAccrued = period.getTransactionAccrued();
|
||||
Money feeAccrued = period.getFeeTransactionAccrued();
|
||||
Money penaltyAccrued = period.getPenaltyTransactionAccrued();
|
||||
LoanRepaymentScheduleInstallment installment = loan.fetchRepaymentScheduleInstallment(period.getInstallmentNumber());
|
||||
installment.updateAccrualPortion(interestAccrued, feeAccrued, penaltyAccrued);
|
||||
}
|
||||
} else {
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
boolean isBasedOnSubmittedOnDate = !isChargeOnDueDate();
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
checkAndUpdateAccrualsForInstallment(loan, accrualTransactions, installments, isBasedOnSubmittedOnDate, installment);
|
||||
}
|
||||
}
|
||||
// reverse accruals after last installment
|
||||
LoanRepaymentScheduleInstallment lastInstallment = loan.getLastLoanRepaymentScheduleInstallment();
|
||||
reverseTransactionsAfter(accrualTransactions, lastInstallment.getDueDate());
|
||||
reverseTransactionsAfter(accrualTransactions, lastDueDate, false);
|
||||
}
|
||||
|
||||
private void reprocessNonPeriodicAccruals(Loan loan, final List<LoanTransaction> accruals) {
|
||||
private void reprocessNonPeriodicAccruals(Loan loan, final List<LoanTransaction> accrualTransactions) {
|
||||
if (isProgressiveAccrual(loan)) {
|
||||
return;
|
||||
}
|
||||
@@ -738,29 +725,25 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
ExternalId externalId = ExternalId.empty();
|
||||
boolean isExternalIdAutoGenerationEnabled = configurationDomainService.isExternalIdAutoGenerationEnabled();
|
||||
|
||||
for (LoanTransaction loanTransaction : accruals) {
|
||||
if (loanTransaction.getInterestPortion(loan.getCurrency()).isGreaterThanZero()) {
|
||||
if (loanTransaction.getInterestPortion(loan.getCurrency()).isNotEqualTo(interestApplied)) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction,
|
||||
"reversed");
|
||||
loanTransaction.reverse();
|
||||
for (LoanTransaction accrualTransaction : accrualTransactions) {
|
||||
if (accrualTransaction.getInterestPortion(loan.getCurrency()).isGreaterThanZero()) {
|
||||
if (accrualTransaction.getInterestPortion(loan.getCurrency()).isNotEqualTo(interestApplied)) {
|
||||
accrualTransaction.reverse();
|
||||
if (isExternalIdAutoGenerationEnabled) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(loan.getOffice(), loan,
|
||||
final LoanTransaction interestAccrualTransaction = LoanTransaction.accrueInterest(loan.getOffice(), loan,
|
||||
interestApplied, loan.getDisbursementDate(), externalId);
|
||||
loan.addLoanTransaction(interestAppliedTransaction);
|
||||
loan.addLoanTransaction(interestAccrualTransaction);
|
||||
}
|
||||
} else {
|
||||
Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
|
||||
Set<LoanChargePaidBy> chargePaidBies = accrualTransaction.getLoanChargesPaid();
|
||||
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
|
||||
LoanCharge loanCharge = chargePaidBy.getLoanCharge();
|
||||
Money chargeAmount = loanCharge.getAmount(loan.getCurrency());
|
||||
if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(loan.getCurrency()))) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(),
|
||||
loanTransaction, "reversed");
|
||||
loanTransaction.reverse();
|
||||
loan.handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate());
|
||||
if (chargeAmount.isNotEqualTo(accrualTransaction.getAmount(loan.getCurrency()))) {
|
||||
accrualTransaction.reverse();
|
||||
loan.handleChargeAppliedTransaction(loanCharge, accrualTransaction.getTransactionDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -786,8 +769,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
interest = interest.minus(accrualTransaction.getInterestPortion(currency));
|
||||
fee = fee.minus(accrualTransaction.getFeeChargesPortion(currency));
|
||||
penalty = penalty.minus(accrualTransaction.getPenaltyChargesPortion(currency));
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(accrualTransaction.getLoan(),
|
||||
accrualTransaction, "reversed");
|
||||
accrualTransaction.reverse();
|
||||
}
|
||||
}
|
||||
@@ -869,8 +850,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
|
||||
loan.addLoanTransaction(transaction);
|
||||
} else if (existingIncomeTransaction.getAmount(loan.getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(existingIncomeTransaction.getLoan(),
|
||||
existingIncomeTransaction, "reversed");
|
||||
existingIncomeTransaction.reverse();
|
||||
LoanTransaction transaction = LoanTransaction.incomePosting(loan, loan.getOffice(), compoundingDetail.getEffectiveDate(),
|
||||
compoundingDetail.getAmount(), interest, fee, penalties, externalId);
|
||||
@@ -889,8 +868,6 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
if (existingAccrualTransaction == null
|
||||
|| !MathUtil.isEqualTo(existingAccrualTransaction.getAmount(), compoundingDetail.getAmount())) {
|
||||
if (existingAccrualTransaction != null) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(existingAccrualTransaction.getLoan(),
|
||||
existingAccrualTransaction, "reversed");
|
||||
existingAccrualTransaction.reverse();
|
||||
}
|
||||
LoanTransaction accrual = LoanTransaction.accrueTransaction(loan, loan.getOffice(), compoundingDetail.getEffectiveDate(),
|
||||
@@ -910,8 +887,8 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
&& loan.getStatus().isClosedObligationsMet() && !loan.isNpa() && !loan.isChargedOff()) {
|
||||
|
||||
LocalDate closedDate = loan.getClosedOnDate();
|
||||
reverseTransactionsOnOrAfter(retrieveListOfIncomePostingTransactions(loan), closedDate);
|
||||
reverseTransactionsOnOrAfter(retrieveListOfAccrualTransactions(loan), closedDate);
|
||||
reverseTransactionsOnOrAfter(retrieveListOfIncomePostingTransactions(loan), closedDate, false);
|
||||
reverseTransactionsOnOrAfter(retrieveListOfAccrualTransactions(loan), closedDate, false);
|
||||
|
||||
HashMap<String, BigDecimal> cumulativeIncomeFromInstallments = new HashMap<>();
|
||||
determineCumulativeIncomeFromInstallments(loan, cumulativeIncomeFromInstallments);
|
||||
@@ -1188,36 +1165,34 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
}
|
||||
}
|
||||
|
||||
private boolean reverseTransactionsAfter(List<LoanTransaction> transactions, LocalDate effectiveDate) {
|
||||
private boolean reverseTransactionsAfter(List<LoanTransaction> accrualTransactions, LocalDate effectiveDate, boolean addEvent) {
|
||||
boolean reversed = false;
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
if (DateUtils.isAfter(loanTransaction.getTransactionDate(), effectiveDate)) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction,
|
||||
"reversed");
|
||||
reverseAccrual(loanTransaction);
|
||||
for (LoanTransaction accrualTransaction : accrualTransactions) {
|
||||
if (!accrualTransaction.isReversed() && DateUtils.isAfter(accrualTransaction.getTransactionDate(), effectiveDate)) {
|
||||
reverseAccrual(accrualTransaction, addEvent);
|
||||
reversed = true;
|
||||
}
|
||||
}
|
||||
return reversed;
|
||||
}
|
||||
|
||||
private boolean reverseTransactionsOnOrAfter(List<LoanTransaction> transactions, LocalDate date) {
|
||||
private boolean reverseTransactionsOnOrAfter(List<LoanTransaction> accrualTransactions, LocalDate date, boolean addEvent) {
|
||||
boolean reversed = false;
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
if (!DateUtils.isBefore(loanTransaction.getTransactionDate(), date)) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction,
|
||||
"reversed");
|
||||
reverseAccrual(loanTransaction);
|
||||
for (LoanTransaction accrualTransaction : accrualTransactions) {
|
||||
if (!accrualTransaction.isReversed() && !DateUtils.isBefore(accrualTransaction.getTransactionDate(), date)) {
|
||||
reverseAccrual(accrualTransaction, addEvent);
|
||||
reversed = true;
|
||||
}
|
||||
}
|
||||
return reversed;
|
||||
}
|
||||
|
||||
private void reverseAccrual(LoanTransaction transaction) {
|
||||
private void reverseAccrual(LoanTransaction transaction, boolean addEvent) {
|
||||
transaction.reverse();
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
if (addEvent) {
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate getFinalAccrualTransactionDate(Loan loan) {
|
||||
@@ -1229,7 +1204,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
}
|
||||
|
||||
public boolean isProgressiveAccrual(@NotNull Loan loan) {
|
||||
return loan.getLoanProductRelatedDetail().getLoanScheduleType() == LoanScheduleType.PROGRESSIVE;
|
||||
return loan.isProgressiveSchedule();
|
||||
}
|
||||
|
||||
private void setSetHelpers(Loan loan) {
|
||||
|
||||
+14
-11
@@ -301,17 +301,18 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && isAppliedOnBackDate
|
||||
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false);
|
||||
}
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
||||
.withEntityId(loanCharge.getId()) //
|
||||
.withEntityExternalId(loanCharge.getExternalId()) //
|
||||
@@ -825,14 +826,14 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
loan = loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && runInterestRecalculation
|
||||
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, true, false);
|
||||
}
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1050,9 +1051,11 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
|| (configurationDomainService.isImmediateChargeAccrualPostMaturityEnabled()
|
||||
&& DateUtils.getBusinessLocalDate().isAfter(loan.getMaturityDate()))) {
|
||||
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null);
|
||||
this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(applyLoanChargeTransaction));
|
||||
if (applyLoanChargeTransaction != null) {
|
||||
this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction);
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(applyLoanChargeTransaction));
|
||||
}
|
||||
}
|
||||
return DateUtils.isBeforeBusinessDate(loanCharge.getDueLocalDate());
|
||||
}
|
||||
|
||||
+3
-13
@@ -32,7 +32,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlatformService {
|
||||
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService;
|
||||
|
||||
@PostConstruct
|
||||
@@ -42,21 +41,14 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat
|
||||
new LoanAccrualActivityPostingLoanStatusChangedListener());
|
||||
}
|
||||
|
||||
private final class LoanStatusChangedListener implements BusinessEventListener<LoanStatusChangedBusinessEvent> {
|
||||
private static final class LoanStatusChangedListener implements BusinessEventListener<LoanStatusChangedBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(LoanStatusChangedBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
log.debug("Loan Status change for loan {}", loan.getId());
|
||||
LoanStatus oldStatus = event.getOldStatus();
|
||||
LoanStatus newStatus = loan.getStatus();
|
||||
if (oldStatus.isActive() && (newStatus.isClosedObligationsMet() || newStatus.isOverpaid())) {
|
||||
log.debug("Loan Status {} for loan {}", newStatus.getCode(), loan.getId());
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan);
|
||||
} else if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) {
|
||||
if (loan.isOpen()) {
|
||||
loan.handleMaturityDateActivate();
|
||||
loanAccrualsProcessingService.processAccrualsOnLoanReopen(loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,9 +62,7 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat
|
||||
if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
LoanStatus oldStatus = event.getOldStatus();
|
||||
LoanStatus newStatus = loan.getStatus();
|
||||
if (oldStatus.isActive() && (newStatus.isClosedObligationsMet() || newStatus.isOverpaid())) {
|
||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan);
|
||||
} else if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) {
|
||||
if ((oldStatus.isClosed() || oldStatus.isOverpaid()) && newStatus.isActive()) {
|
||||
loanAccrualActivityProcessingService.processAccrualActivityForLoanReopen(loan);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-10
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
@@ -26,7 +27,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
@@ -68,16 +69,17 @@ public class LoanTransactionAssembler {
|
||||
txnExternalId, null);
|
||||
}
|
||||
|
||||
public LoanTransaction assembleAccrualActivityTransaction(Loan loan, final LoanRepaymentScheduleInstallment installment,
|
||||
final LocalDate transactionDate) {
|
||||
MonetaryCurrency currency = loan.getCurrency();
|
||||
public LoanTransaction assembleAccrualActivityTransaction(@NotNull Loan loan, @NotNull LoanRepaymentScheduleInstallment installment,
|
||||
@NotNull LocalDate transactionDate) {
|
||||
ExternalId externalId = externalIdFactory.create();
|
||||
|
||||
BigDecimal interestPortion = installment.getInterestCharged(currency).getAmount();
|
||||
BigDecimal feeChargesPortion = installment.getFeeChargesCharged(currency).getAmount();
|
||||
BigDecimal penaltyChargesPortion = installment.getPenaltyChargesCharged(currency).getAmount();
|
||||
BigDecimal transactionAmount = interestPortion.add(feeChargesPortion).add(penaltyChargesPortion);
|
||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), transactionDate,
|
||||
transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId);
|
||||
BigDecimal interestPortion = installment.getInterestCharged();
|
||||
BigDecimal feeChargesPortion = installment.getFeeChargesCharged();
|
||||
BigDecimal penaltyChargesPortion = installment.getPenaltyCharges();
|
||||
BigDecimal transactionAmount = MathUtil.add(interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||
return MathUtil.isGreaterThanZero(transactionAmount)
|
||||
? new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), transactionDate,
|
||||
transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
+93
-145
@@ -95,8 +95,6 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanWithdra
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent;
|
||||
@@ -230,7 +228,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@RequiredArgsConstructor
|
||||
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
|
||||
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
||||
private final PlatformSecurityContext context;
|
||||
private final LoanTransactionValidator loanTransactionValidator;
|
||||
private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer;
|
||||
@@ -499,9 +496,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
createNote(loan, command, changes);
|
||||
// auto create standing instruction
|
||||
createStandingInstruction(loan);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
}
|
||||
|
||||
final Set<LoanCharge> loanCharges = loan.getActiveCharges();
|
||||
@@ -526,8 +520,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
// Post Dated Checks
|
||||
@@ -551,6 +545,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(loan.getId()) //
|
||||
@@ -869,8 +866,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), true);
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalBusinessEvent(loan));
|
||||
}
|
||||
@@ -1085,8 +1082,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private ChangedTransactionDetail recalculateLoanWithInterestPaymentWaiverTxn(Loan loan,
|
||||
LoanTransaction newInterestPaymentWaiverTransaction) {
|
||||
LocalDate recalculateFrom = null;
|
||||
LocalDate transactionDate = newInterestPaymentWaiverTransaction.getTransactionDate();
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
recalculateFrom = newInterestPaymentWaiverTransaction.getTransactionDate();
|
||||
recalculateFrom = transactionDate;
|
||||
}
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null);
|
||||
|
||||
@@ -1102,14 +1100,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loan.getTransactionProcessor();
|
||||
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = loan
|
||||
.fetchLoanRepaymentScheduleInstallmentByDueDate(newInterestPaymentWaiverTransaction.getTransactionDate());
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate);
|
||||
|
||||
boolean reprocess = true;
|
||||
|
||||
if (!loan.isForeclosure() && isTransactionChronologicallyLatest
|
||||
&& DateUtils.isEqualBusinessDate(newInterestPaymentWaiverTransaction.getTransactionDate()) && currentInstallment != null
|
||||
&& currentInstallment.getTotalOutstanding(loan.getCurrency())
|
||||
if (!loan.isForeclosure() && isTransactionChronologicallyLatest && DateUtils.isEqualBusinessDate(transactionDate)
|
||||
&& currentInstallment != null && currentInstallment.getTotalOutstanding(loan.getCurrency())
|
||||
.isEqualTo(newInterestPaymentWaiverTransaction.getAmount(loan.getCurrency()))) {
|
||||
reprocess = false;
|
||||
}
|
||||
@@ -1155,66 +1150,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
return loan.reprocessTransactions();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public Loan reverseReplayAccrualActivityTransaction(Loan loan, final LoanTransaction loanTransaction,
|
||||
final LoanRepaymentScheduleInstallment installment, final LocalDate transactionDate) {
|
||||
|
||||
LoanTransaction newLoanTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
|
||||
transactionDate);
|
||||
if (!newLoanTransaction.getDateOf().isEqual(loanTransaction.getDateOf())
|
||||
|| !LoanTransaction.transactionAmountsMatch(loan.getCurrency(), loanTransaction, newLoanTransaction)) {
|
||||
loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loanTransaction.getLoan(), loanTransaction,
|
||||
"reversed");
|
||||
loanTransaction.reverse();
|
||||
loanTransaction.updateExternalId(null);
|
||||
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
||||
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newLoanTransaction);
|
||||
loan.addLoanTransaction(newLoanTransaction);
|
||||
|
||||
LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(loanTransaction);
|
||||
data.setNewTransactionDetail(newLoanTransaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
}
|
||||
return loan;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public Loan makeAccrualActivityTransaction(Loan loan, final LoanRepaymentScheduleInstallment installment,
|
||||
final LocalDate transactionDate) {
|
||||
|
||||
LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment,
|
||||
transactionDate);
|
||||
|
||||
if (newAccrualActivityTransaction.getAmount().compareTo(BigDecimal.ZERO) == 0) {
|
||||
return loan;
|
||||
}
|
||||
|
||||
return makeAccrualActivityTransaction(loan, newAccrualActivityTransaction);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public Loan makeAccrualActivityTransaction(Loan loan, LoanTransaction newAccrualActivityTransaction) {
|
||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan));
|
||||
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction);
|
||||
|
||||
loan.addLoanTransaction(newAccrualActivityTransaction);
|
||||
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanTransactionAccrualActivityPostBusinessEvent(newAccrualActivityTransaction));
|
||||
|
||||
return loan;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult makeInterestPaymentWaiver(final JsonCommand command) {
|
||||
|
||||
loanTransactionValidator.validateLoanTransactionInterestPaymentWaiver(command);
|
||||
|
||||
final Long loanId = command.getLoanId();
|
||||
@@ -1245,10 +1183,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
saveNote(noteText, loan, newInterestPaymentWaiverTransaction);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, newInterestPaymentWaiverTransaction.getTransactionDate());
|
||||
|
||||
// disable all active standing orders linked to this loan if status changes to closed
|
||||
@@ -1264,6 +1200,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(transactionRepaymentEvent);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
||||
.withLoanId(loan.getId()) //
|
||||
.withEntityId(newInterestPaymentWaiverTransaction.getId()) //
|
||||
@@ -1564,11 +1503,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loan.updateLoanSummaryAndStatus();
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
@@ -1586,6 +1522,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(eventData));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(entityId) //
|
||||
@@ -1647,9 +1586,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
writeOffTransaction.reverse();
|
||||
}
|
||||
|
||||
if (loan.isClosedObligationsMet() || loan.isClosedWrittenOff() || loan.isClosedWithOutstandingAmountMarkedForReschedule()) {
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_ADJUST_TRANSACTION, loan);
|
||||
}
|
||||
loan.updateLoanSummaryAndStatus();
|
||||
|
||||
if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) {
|
||||
changedTransactionDetail = loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan,
|
||||
@@ -1850,14 +1787,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(waiveInterestTransaction.getId()) //
|
||||
@@ -1918,44 +1856,48 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.WRITE_OFF_OUTSTANDING);
|
||||
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.WRITE_OFF_OUTSTANDING,
|
||||
writtenOffOnLocalDate);
|
||||
final ChangedTransactionDetail changedTransactionDetail = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes,
|
||||
existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
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());
|
||||
}
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
changes.put("note", noteText);
|
||||
final Note note = Note.loanTransactionNote(loan, writeOff, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff));
|
||||
return new CommandProcessingResultBuilder() //
|
||||
CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(writeOff.getId()) //
|
||||
.withEntityExternalId(writeOff.getExternalId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
.withGroupId(loan.getGroupId()) //
|
||||
.withLoanId(loanId) //
|
||||
.with(changes).build();
|
||||
.with(changes);
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes,
|
||||
existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
|
||||
|
||||
if (changedTransactionDetail != null) {
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
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());
|
||||
}
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
changes.put("note", noteText);
|
||||
final Note note = Note.loanTransactionNote(loan, writeOff, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
builder.withEntityId(writeOff.getId()).withEntityExternalId(writeOff.getExternalId());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -2020,12 +1962,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
|
||||
if (possibleClosingTransaction != null) {
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
@@ -2037,6 +1976,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
// disable all active standing instructions linked to the loan
|
||||
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
CommandProcessingResult result;
|
||||
if (possibleClosingTransaction != null) {
|
||||
|
||||
@@ -2654,15 +2596,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction));
|
||||
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
if (writeOffTransaction != null) {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction));
|
||||
}
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
@@ -2811,11 +2755,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
createLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||
}
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
@@ -2879,11 +2825,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||
}
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan));
|
||||
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan));
|
||||
return loan;
|
||||
}
|
||||
|
||||
@@ -3396,8 +3343,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
// Create journal entries for the new transaction(s)
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
// Calculate and apply delinquency tag (if applicable)
|
||||
loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate);
|
||||
// disable all active standing orders linked to this loan if status
|
||||
@@ -3414,11 +3359,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, refundTransaction);
|
||||
}
|
||||
// Raise business events
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
|
||||
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan,
|
||||
loan.repaymentScheduleDetail().isInterestRecalculationEnabled(), false);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
|
||||
|
||||
// Create journal entries for the new transaction(s)
|
||||
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
|
||||
|
||||
Long entityId = refundTransaction.getId();
|
||||
ExternalId entityExternalId = refundTransaction.getExternalId();
|
||||
Long subEntityId = interestRefundTransaction != null ? interestRefundTransaction.getId() : null;
|
||||
|
||||
+22
-15
@@ -100,6 +100,7 @@ import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlat
|
||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualActivityProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualEventService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||
@@ -332,9 +333,16 @@ public class LoanAccountConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanStatusChangePlatformService.class)
|
||||
public LoanStatusChangePlatformService loanStatusChangePlatformService(BusinessEventNotifierService businessEventNotifierService,
|
||||
LoanAccrualActivityProcessingService loanAccrualActivityProcessingService) {
|
||||
return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccrualActivityProcessingService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanAccrualEventService.class)
|
||||
public LoanAccrualEventService loanAccrualEventService(BusinessEventNotifierService businessEventNotifierService,
|
||||
LoanAccrualsProcessingService loanAccrualsProcessingService,
|
||||
LoanAccrualActivityProcessingService loanAccrualActivityProcessingService) {
|
||||
return new LoanStatusChangePlatformServiceImpl(businessEventNotifierService, loanAccrualsProcessingService,
|
||||
return new LoanAccrualEventService(businessEventNotifierService, loanAccrualsProcessingService,
|
||||
loanAccrualActivityProcessingService);
|
||||
}
|
||||
|
||||
@@ -351,8 +359,8 @@ public class LoanAccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanWritePlatformService.class)
|
||||
public LoanWritePlatformService loanWritePlatformService(LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory,
|
||||
PlatformSecurityContext context, LoanTransactionValidator loanTransactionValidator,
|
||||
public LoanWritePlatformService loanWritePlatformService(PlatformSecurityContext context,
|
||||
LoanTransactionValidator loanTransactionValidator,
|
||||
LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper,
|
||||
LoanAccountDomainService loanAccountDomainService, NoteRepository noteRepository,
|
||||
LoanTransactionRepository loanTransactionRepository, LoanTransactionRelationRepository loanTransactionRelationRepository,
|
||||
@@ -381,18 +389,17 @@ public class LoanAccountConfiguration {
|
||||
LoanOfficerValidator loanOfficerValidator, LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator,
|
||||
LoanDisbursementService loanDisbursementService, LoanScheduleService loanScheduleService,
|
||||
LoanChargeValidator loanChargeValidator, LoanOfficerService loanOfficerService) {
|
||||
return new LoanWritePlatformServiceJpaRepositoryImpl(transactionProcessorFactory, context, loanTransactionValidator,
|
||||
loanUpdateCommandFromApiJsonDeserializer, loanRepositoryWrapper, loanAccountDomainService, noteRepository,
|
||||
loanTransactionRepository, loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService,
|
||||
calendarInstanceRepository, paymentDetailWritePlatformService, holidayRepository, configurationDomainService,
|
||||
workingDaysRepository, accountTransfersWritePlatformService, accountTransfersReadPlatformService,
|
||||
accountAssociationsReadPlatformService, loanReadPlatformService, fromApiJsonHelper, calendarRepository,
|
||||
loanScheduleHistoryWritePlatformService, loanApplicationValidator, accountAssociationRepository,
|
||||
accountTransferDetailRepository, businessEventNotifierService, guarantorDomainService, loanUtilService, loanSummaryWrapper,
|
||||
entityDatatableChecksWritePlatformService, transactionProcessingStrategy, codeValueRepository,
|
||||
cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler,
|
||||
postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, defaultLoanLifecycleStateMachine,
|
||||
loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
|
||||
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
|
||||
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
|
||||
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
|
||||
paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository,
|
||||
accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService,
|
||||
loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService,
|
||||
loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService,
|
||||
guarantorDomainService, loanUtilService, loanSummaryWrapper, 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);
|
||||
|
||||
+3
@@ -128,6 +128,9 @@ class LoanChargeWritePlatformServiceImplTest {
|
||||
@Mock
|
||||
private LoanChargeValidator loanChargeValidator;
|
||||
|
||||
@Mock
|
||||
private LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan);
|
||||
|
||||
-4
@@ -240,8 +240,6 @@ class LoanWritePlatformServiceJpaRepositoryImplTest {
|
||||
when(loan.findExistingReversedTransactionIds()).thenReturn(Collections.emptyList());
|
||||
doNothing().when(loanTransactionValidator).validateActivityNotBeforeClientOrGroupTransferDate(any(), any(), any());
|
||||
when(loan.isClosedWrittenOff()).thenReturn(false);
|
||||
when(loan.isClosedObligationsMet()).thenReturn(false);
|
||||
when(loan.isClosedWithOutstandingAmountMarkedForReschedule()).thenReturn(false);
|
||||
when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true);
|
||||
|
||||
// Act
|
||||
@@ -287,8 +285,6 @@ class LoanWritePlatformServiceJpaRepositoryImplTest {
|
||||
when(loan.findExistingReversedTransactionIds()).thenReturn(Collections.emptyList());
|
||||
doNothing().when(loanTransactionValidator).validateActivityNotBeforeClientOrGroupTransferDate(any(), any(), any());
|
||||
when(loan.isClosedWrittenOff()).thenReturn(false);
|
||||
when(loan.isClosedObligationsMet()).thenReturn(false);
|
||||
when(loan.isClosedWithOutstandingAmountMarkedForReschedule()).thenReturn(false);
|
||||
when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true);
|
||||
|
||||
// Act
|
||||
|
||||
Reference in New Issue
Block a user