FINERACT-2107: Extract Merchant issued refund and Payout refund handling

This commit is contained in:
Adam Saghy
2024-10-24 15:51:41 +02:00
parent 393f4398ac
commit 4198b0bcf9
10 changed files with 386 additions and 236 deletions
@@ -2352,7 +2352,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
loanTransaction.updateLoan(this); loanTransaction.updateLoan(this);
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction, getLoanTransactions()); final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction);
if (loanTransaction.isNotZero(loanCurrency())) { if (loanTransaction.isNotZero(loanCurrency())) {
addLoanTransaction(loanTransaction); addLoanTransaction(loanTransaction);
@@ -2396,7 +2396,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode); .determineProcessor(this.transactionProcessingStrategyCode);
final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment( final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallmentByDueDate(
loanTransaction.getTransactionDate()); loanTransaction.getTransactionDate());
boolean reprocess = isForeclosure() || !isTransactionChronologicallyLatest || adjustedTransaction != null boolean reprocess = isForeclosure() || !isTransactionChronologicallyLatest || adjustedTransaction != null
@@ -2471,7 +2471,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return loanTransactionDate; return loanTransactionDate;
} }
public LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallment(LocalDate dueDate) { public LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallmentByDueDate(LocalDate dueDate) {
return getRepaymentScheduleInstallments().stream() // return getRepaymentScheduleInstallments().stream() //
.filter(installment -> dueDate.equals(installment.getDueDate())).findFirst() // .filter(installment -> dueDate.equals(installment.getDueDate())).findFirst() //
.orElse(null); .orElse(null);
@@ -2504,8 +2504,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList()); .sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
} }
private boolean doPostLoanTransactionChecks(final LocalDate transactionDate, public boolean doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
boolean statusChanged = false; boolean statusChanged = false;
boolean isOverpaid = MathUtil.isGreaterThanZero(totalOverpaid); boolean isOverpaid = MathUtil.isGreaterThanZero(totalOverpaid);
if (isOverpaid) { if (isOverpaid) {
@@ -2549,8 +2548,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
this.actualMaturityDate = null; this.actualMaturityDate = null;
} }
private boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction, public boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction) {
final List<LoanTransaction> loanTransactions) {
boolean isChronologicallyLatestRepaymentOrWaiver = true; boolean isChronologicallyLatestRepaymentOrWaiver = true;
final LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
@@ -23,11 +23,13 @@ import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyActionData; import org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyActionData;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData; import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
public interface LoanAccountDomainService { public interface LoanAccountDomainService {
@@ -54,8 +56,6 @@ public interface LoanAccountDomainService {
LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId); BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId);
void updateLoanCollateralTransaction(Set<LoanCollateralManagement> loanCollateralManagementList);
void updateLoanCollateralStatus(Set<LoanCollateralManagement> loanCollateralManagementSet, boolean isReleased); void updateLoanCollateralStatus(Set<LoanCollateralManagement> loanCollateralManagementSet, boolean isReleased);
/** /**
@@ -94,6 +94,13 @@ public interface LoanAccountDomainService {
LoanTransaction creditBalanceRefund(Loan loan, LocalDate transactionDate, BigDecimal transactionAmount, String noteText, LoanTransaction creditBalanceRefund(Loan loan, LocalDate transactionDate, BigDecimal transactionAmount, String noteText,
ExternalId externalId, PaymentDetail paymentDetail); ExternalId externalId, PaymentDetail paymentDetail);
Pair<LoanTransaction, LoanTransaction> makeRefund(Loan loan, ScheduleGeneratorDTO scheduleGeneratorDTO,
LoanTransactionType loanTransactionType, LocalDate transactionDate, BigDecimal transactionAmount, PaymentDetail paymentDetail,
ExternalId txnExternalId);
void updateAndSavePostDatedChecksForIndividualAccount(Loan loan, LoanTransaction transaction);
LoanTransaction applyInterestRefund(Loan loan, LoanRefundRequestData loanRefundRequest); LoanTransaction applyInterestRefund(Loan loan, LoanRefundRequestData loanRefundRequest);
void updateAndSaveLoanCollateralTransactionsForIndividualAccounts(Loan loan, LoanTransaction transaction);
} }
@@ -206,6 +206,12 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
externalId); externalId);
} }
public static LoanTransaction refund(Loan loan, LoanTransactionType loanTransactionType, BigDecimal transactionAmount,
PaymentDetail paymentDetail, LocalDate transactionDate, ExternalId txnExternalId) {
return new LoanTransaction(loan, loan.getOffice(), loanTransactionType, paymentDetail, transactionAmount, transactionDate,
txnExternalId);
}
public void setLoanTransactionToRepaymentScheduleMappings(final Integer installmentId, final BigDecimal chargePerInstallment) { public void setLoanTransactionToRepaymentScheduleMappings(final Integer installmentId, final BigDecimal chargePerInstallment) {
for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : this.loanTransactionToRepaymentScheduleMappings) { for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : this.loanTransactionToRepaymentScheduleMappings) {
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping
@@ -21,13 +21,10 @@ package org.apache.fineract.portfolio.loanaccount.handler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.DataIntegrityErrorHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -37,19 +34,10 @@ import org.springframework.transaction.annotation.Transactional;
public class LoanMerchantIssuedRefundCommandHandler implements NewCommandSourceHandler { public class LoanMerchantIssuedRefundCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService; private final LoanWritePlatformService writePlatformService;
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
try { return this.writePlatformService.makeRefund(command.getLoanId(), LoanTransactionType.MERCHANT_ISSUED_REFUND, command);
boolean isRecoveryRepayment = false;
return this.writePlatformService.makeLoanRepayment(LoanTransactionType.MERCHANT_ISSUED_REFUND, command.getLoanId(), command,
isRecoveryRepayment);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
dataIntegrityErrorHandler.handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.merchantIssuedRefund",
"Merchant Issued Refund");
return CommandProcessingResult.empty();
}
} }
} }
@@ -21,13 +21,10 @@ package org.apache.fineract.portfolio.loanaccount.handler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.DataIntegrityErrorHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -37,19 +34,10 @@ import org.springframework.transaction.annotation.Transactional;
public class LoanPayoutRefundCommandHandler implements NewCommandSourceHandler { public class LoanPayoutRefundCommandHandler implements NewCommandSourceHandler {
private final LoanWritePlatformService writePlatformService; private final LoanWritePlatformService writePlatformService;
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
@Transactional @Transactional
@Override @Override
public CommandProcessingResult processCommand(final JsonCommand command) { public CommandProcessingResult processCommand(final JsonCommand command) {
try { return this.writePlatformService.makeRefund(command.getLoanId(), LoanTransactionType.PAYOUT_REFUND, command);
boolean isRecoveryRepayment = false;
return this.writePlatformService.makeLoanRepayment(LoanTransactionType.PAYOUT_REFUND, command.getLoanId(), command,
isRecoveryRepayment);
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
dataIntegrityErrorHandler.handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.payoutRefund",
"Payout Refund");
return CommandProcessingResult.empty();
}
} }
} }
@@ -134,4 +134,6 @@ public interface LoanWritePlatformService {
@Transactional @Transactional
CommandProcessingResult undoChargeOff(JsonCommand command); CommandProcessingResult undoChargeOff(JsonCommand command);
CommandProcessingResult makeRefund(Long loanId, LoanTransactionType loanTransactionType, JsonCommand command);
} }
@@ -28,6 +28,7 @@ import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
@@ -80,6 +81,7 @@ import org.apache.fineract.portfolio.account.domain.StandingInstructionRepositor
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction; import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
import org.apache.fineract.portfolio.delinquency.helper.DelinquencyEffectivePauseHelper; import org.apache.fineract.portfolio.delinquency.helper.DelinquencyEffectivePauseHelper;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
@@ -91,6 +93,9 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData; import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService; import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDelegate; import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDelegate;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
@@ -139,6 +144,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final DelinquencyReadPlatformService delinquencyReadPlatformService; private final DelinquencyReadPlatformService delinquencyReadPlatformService;
private final LoanAccrualsProcessingService loanAccrualsProcessingService; private final LoanAccrualsProcessingService loanAccrualsProcessingService;
private final InterestRefundServiceDelegate interestRefundServiceDelegate; private final InterestRefundServiceDelegate interestRefundServiceDelegate;
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
@Transactional @Transactional
@Override @Override
@@ -150,12 +156,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone, false); isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone, false);
} }
@Transactional
@Override
public void updateLoanCollateralTransaction(Set<LoanCollateralManagement> loanCollateralManagementSet) {
this.loanCollateralManagementRepository.saveAll(loanCollateralManagementSet);
}
@Transactional @Transactional
@Override @Override
public void updateLoanCollateralStatus(Set<LoanCollateralManagement> loanCollateralManagementSet, boolean isReleased) { public void updateLoanCollateralStatus(Set<LoanCollateralManagement> loanCollateralManagementSet, boolean isReleased) {
@@ -165,8 +165,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.loanCollateralManagementRepository.saveAll(loanCollateralManagementSet); this.loanCollateralManagementRepository.saveAll(loanCollateralManagementSet);
} }
@Transactional private LoanTransaction createInterestRefundLoanTransaction(Loan loan, final LocalDate transactionDate,
public LoanTransaction createInterestRefundLoanTransaction(Loan loan, final LocalDate transactionDate,
BigDecimal relatedRefundTransactionAmount) { BigDecimal relatedRefundTransactionAmount) {
InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan); InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan);
if (interestRefundService == null) { if (interestRefundService == null) {
@@ -174,15 +173,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
BigDecimal interestRefundAmount = interestRefundService.calculateInterestRefundAmount(loan.getId(), relatedRefundTransactionAmount, BigDecimal interestRefundAmount = interestRefundService.calculateInterestRefundAmount(loan.getId(), relatedRefundTransactionAmount,
transactionDate); transactionDate);
final ExternalId txnExternalId = externalIdFactory.create(); final ExternalId txnExternalId = externalIdFactory.create();
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan));
return LoanTransaction.interestRefund(loan, interestRefundAmount, transactionDate, txnExternalId);
LoanTransaction newInterestRefundTransaction;
newInterestRefundTransaction = LoanTransaction.interestRefund(loan, interestRefundAmount, transactionDate, txnExternalId);
return newInterestRefundTransaction;
} }
@Transactional @Transactional
@@ -225,18 +218,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom,
holidayDetailDto); holidayDetailDto);
boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
.map(LoanSupportedInterestRefundTypes::getTransactionType)
.anyMatch(transactionType -> transactionType.equals(repaymentTransactionType));
LoanTransaction newInterestRefundTransaction = null;
if (shouldCreateInterestRefundTransaction) {
newInterestRefundTransaction = createInterestRefundLoanTransaction(loan, transactionDate, transactionAmount);
if (newInterestRefundTransaction != null) {
loan.addLoanTransaction(newInterestRefundTransaction);
}
}
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment, defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
scheduleGeneratorDTO, isHolidayValidationDone); scheduleGeneratorDTO, isHolidayValidationDone);
@@ -247,9 +228,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction); saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
if (newInterestRefundTransaction != null) {
saveLoanTransactionWithDataIntegrityViolationChecks(newInterestRefundTransaction);
}
/*** /***
* TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the * TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for the
@@ -259,7 +237,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
// Trigger transaction replayed event // Trigger transaction replayed event
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
@@ -316,10 +294,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
} }
} }
if (shouldCreateInterestRefundTransaction && newInterestRefundTransaction != null) {
businessEventNotifierService
.notifyPostBusinessEvent(new LoanTransactionInterestRefundPostBusinessEvent(newInterestRefundTransaction));
}
return newRepaymentTransaction; return newRepaymentTransaction;
} }
@@ -649,7 +624,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
} }
private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { private void updateLoanTransferTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
if (transferTransaction != null) { if (transferTransaction != null) {
transferTransaction.updateToLoanTransaction(newLoanTransaction); transferTransaction.updateToLoanTransaction(newLoanTransaction);
@@ -810,7 +785,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
// Trigger transaction replayed event // Trigger transaction replayed event
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
@@ -847,6 +822,158 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
} }
@Override
public void updateAndSaveLoanCollateralTransactionsForIndividualAccounts(Loan loan, LoanTransaction loanTransaction) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
if (loanTransaction != null) {
loanCollateralManagement.setLoanTransactionData(loanTransaction);
}
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
if (loan.getStatus().isClosed()) {
loanCollateralManagement.setIsReleased(true);
BigDecimal quantity = loanCollateralManagement.getQuantity();
clientCollateralManagement.updateQuantity(clientCollateralManagement.getQuantity().add(quantity));
loanCollateralManagement.setClientCollateralManagement(clientCollateralManagement);
}
}
this.loanCollateralManagementRepository.saveAll(loanCollateralManagements);
}
}
@Override
public Pair<LoanTransaction, LoanTransaction> makeRefund(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO,
final LoanTransactionType loanTransactionType, final LocalDate transactionDate, final BigDecimal transactionAmount,
final PaymentDetail paymentDetail, final ExternalId txnExternalId) {
// Pre-processing business event
switch (loanTransactionType) {
case MERCHANT_ISSUED_REFUND ->
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionMerchantIssuedRefundPreBusinessEvent(loan));
case PAYOUT_REFUND ->
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionPayoutRefundPreBusinessEvent(loan));
default ->
throw new UnsupportedOperationException(String.format("Not configured loan transaction type: %s!", loanTransactionType));
}
LoanTransaction refundTransaction = LoanTransaction.refund(loan, loanTransactionType, transactionAmount, paymentDetail,
transactionDate, txnExternalId);
final boolean isTransactionChronologicallyLatest = loan.isChronologicallyLatestRepaymentOrWaiver(refundTransaction);
loan.getLoanTransactions().add(refundTransaction);
boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
.map(LoanSupportedInterestRefundTypes::getTransactionType)
.anyMatch(transactionType -> transactionType.equals(loanTransactionType));
LoanTransaction interestRefundTransaction = null;
if (shouldCreateInterestRefundTransaction) {
interestRefundTransaction = createInterestRefundLoanTransaction(loan, transactionDate, transactionAmount);
if (interestRefundTransaction != null) {
loan.addLoanTransaction(interestRefundTransaction);
}
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = transactionProcessorFactory
.determineProcessor(loan.getTransactionProcessingStrategyCode());
final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate);
boolean reprocess = loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled() //
|| loan.isForeclosure() //
|| !isTransactionChronologicallyLatest //
|| !DateUtils.isEqualBusinessDate(transactionDate) //
|| currentInstallment == null //
|| !currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(refundTransaction.getAmount(loan.getCurrency())); //
if (!reprocess) {
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(refundTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
} else {
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
loan.getDisbursementDate(), allNonContraTransactionsPostDisbursement, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(loan);
}
loan.getLoanTransactions().addAll(changedTransactionDetail.getNewTransactionMappings().values());
// Store and flush newly created transaction to generate PK
saveLoanTransactionWithDataIntegrityViolationChecks(refundTransaction);
if (interestRefundTransaction != null) {
saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction);
}
/*
* TODO Vishwas Batch save is giving me a HibernateOptimisticLockingFailureException, looping and saving for
* the time being, not a major issue for now as this loop is entered only in edge cases (when a payment is
* made before the latest payment recorded against the loan)
*/
// Apply reprocessed transactions (if applicable)
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue());
}
// Trigger transaction replayed event
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
}
loan.updateLoanSummaryDerivedFields();
loan.doPostLoanTransactionChecks(transactionDate, defaultLoanLifecycleStateMachine);
switch (loanTransactionType) {
case MERCHANT_ISSUED_REFUND -> businessEventNotifierService
.notifyPostBusinessEvent(new LoanTransactionMerchantIssuedRefundPostBusinessEvent(refundTransaction));
case PAYOUT_REFUND ->
businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionPayoutRefundPostBusinessEvent(refundTransaction));
default ->
throw new UnsupportedOperationException(String.format("Not configured loan transaction type: %s!", loanTransactionType));
}
if (interestRefundTransaction != null) {
businessEventNotifierService
.notifyPostBusinessEvent(new LoanTransactionInterestRefundPostBusinessEvent(interestRefundTransaction));
}
return Pair.of(refundTransaction, interestRefundTransaction);
}
@Override
public void updateAndSavePostDatedChecksForIndividualAccount(final Loan loan, final LoanTransaction transaction) {
if (loan.getLoanType().isIndividualAccount()) {
// Mark Post Dated Check as paid.
final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = transaction
.getLoanTransactionToRepaymentScheduleMappings();
if (loanTransactionToRepaymentScheduleMappings != null) {
for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : loanTransactionToRepaymentScheduleMappings) {
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping
.getLoanRepaymentScheduleInstallment();
if (loanRepaymentScheduleInstallment != null) {
final boolean isPaid = loanRepaymentScheduleInstallment.isNotFullyPaidOff();
PostDatedChecks postDatedChecks = this.postDatedChecksRepository
.getPendingPostDatedCheck(loanRepaymentScheduleInstallment);
if (postDatedChecks != null) {
if (!isPaid) {
postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PAID);
} else {
postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PENDING);
}
this.postDatedChecksRepository.saveAndFlush(postDatedChecks);
} else {
break;
}
}
}
}
}
}
@Override @Override
public LoanTransaction applyInterestRefund(final Loan loan, final LoanRefundRequestData loanRefundRequest) { public LoanTransaction applyInterestRefund(final Loan loan, final LoanRefundRequestData loanRefundRequest) {
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
@@ -868,5 +995,4 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
return interestRefundTransaction; return interestRefundTransaction;
} }
} }
@@ -60,7 +60,6 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException; import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.exception.LoanCollateralAmountNotSufficientException; import org.apache.fineract.portfolio.collateralmanagement.exception.LoanCollateralAmountNotSufficientException;
@@ -77,6 +76,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException; import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException; import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException; import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
@@ -410,7 +410,10 @@ public final class LoanTransactionValidator {
} }
public void validateNewRepaymentTransaction(final String json) { public void validateNewRepaymentTransaction(final String json) {
validatePaymentTransaction(json);
}
private void validatePaymentTransaction(String json) {
if (StringUtils.isBlank(json)) { if (StringUtils.isBlank(json)) {
throw new InvalidJsonException(); throw new InvalidJsonException();
} }
@@ -439,21 +442,6 @@ public final class LoanTransactionValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
public void validateRepaymentDateWithMeetingDate(final LocalDate repaymentDate, final CalendarInstance calendarInstance) {
if (null != calendarInstance) {
final Calendar calendar = calendarInstance.getCalendar();
if (calendar != null && repaymentDate != null) {
// Disbursement date should fall on a meeting date
if (!CalendarUtils.isValidRecurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
final String errorMessage = "Transaction date '" + repaymentDate.toString() + "' does not fall on a meeting date.";
throw new NotValidRecurringDateException("loan.transaction.date", errorMessage, repaymentDate.toString(),
calendar.getTitle());
}
}
}
}
private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) { private void validatePaymentDetails(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
// Validate all string payment detail fields for max length // Validate all string payment detail fields for max length
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element); final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("paymentTypeId", element);
@@ -874,4 +862,91 @@ public final class LoanTransactionValidator {
} }
} }
} }
public void validateRefund(String json) {
validatePaymentTransaction(json);
}
public void validateRefund(final Loan loan, LoanTransactionType loanTransactionType, final LocalDate transactionDate,
ScheduleGeneratorDTO scheduleGeneratorDTO) {
checkClientOrGroupActive(loan);
validateLoanStatusIsActiveOrFullyPaidOrOverpaid(loan);
validateActivityNotBeforeClientOrGroupTransferDate(loan, transactionDate);
validateRepaymentTypeTransactionNotBeforeAChargeRefund(loan, loanTransactionType, transactionDate);
validateTransactionNotBeforeLastTransactionDate(loan, loanTransactionType, transactionDate);
validateRepaymentDateIsOnHoliday(transactionDate, scheduleGeneratorDTO.getHolidayDetailDTO().isAllowTransactionsOnHoliday(),
scheduleGeneratorDTO.getHolidayDetailDTO().getHolidays());
validateRepaymentDateIsOnNonWorkingDay(transactionDate, scheduleGeneratorDTO.getHolidayDetailDTO().getWorkingDays(),
scheduleGeneratorDTO.getHolidayDetailDTO().isAllowTransactionsOnNonWorkingDay());
validateTransactionShouldNotBeInTheFuture(transactionDate);
validateTransactionAmountNotExceedThresholdForMultiDisburseLoan(loan);
}
private void checkClientOrGroupActive(final Loan loan) {
final Client client = loan.client();
if (client != null && client.isNotActive()) {
throw new ClientNotActiveException(client.getId());
}
final Group group = loan.group();
if (group != null && group.isNotActive()) {
throw new GroupNotActiveException(group.getId());
}
}
private void validateActivityNotBeforeClientOrGroupTransferDate(final Loan loan, final LocalDate transactionDate) {
if (loan.getClient() != null && loan.getClient().getOfficeJoiningDate() != null) {
final LocalDate clientOfficeJoiningDate = loan.getClient().getOfficeJoiningDate();
if (DateUtils.isBefore(transactionDate, clientOfficeJoiningDate)) {
String errorMessage = "The date on which the transaction is made cannot be earlier than client's transfer date to this office";
String action = "repayment.or.waiver";
String postfix = "cannot.be.made.before.client.transfer.date";
throw new InvalidLoanStateTransitionException(action, postfix, errorMessage, clientOfficeJoiningDate);
}
}
}
public void validateRepaymentTypeTransactionNotBeforeAChargeRefund(final Loan loan, final LoanTransactionType loanTransactionType,
final LocalDate transactionDate) {
if (loanTransactionType.isRepaymentType() && !loanTransactionType.isChargeRefund()) {
for (LoanTransaction txn : loan.getLoanTransactions()) {
if (txn.isChargeRefund() && DateUtils.isBefore(transactionDate, txn.getTransactionDate())) {
final String errorMessage = "loan.transaction.cant.be.created.because.later.charge.refund.exists";
final String details = "Loan Transaction: " + loan.getId() + " Can't be created because a Later Charge Refund Exists.";
throw new LoanChargeRefundException(errorMessage, details);
}
}
}
}
private void validateTransactionNotBeforeLastTransactionDate(final Loan loan, LoanTransactionType loanTransactionType,
final LocalDate transactionDate) {
if (!((LoanScheduleType.CUMULATIVE.equals(loan.getLoanProductRelatedDetail().getLoanScheduleType())
&& loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled())
|| loan.getLoanProduct().isHoldGuaranteeFunds())) {
return;
}
LocalDate lastTransactionDate = loan.getLastUserTransactionDate();
String humanReadable;
String action;
switch (loanTransactionType) {
case MERCHANT_ISSUED_REFUND -> {
humanReadable = "merchant issued refund";
action = "merchant.issued.refund";
}
case PAYOUT_REFUND -> {
humanReadable = "payout refund";
action = "payout.refund";
}
default -> {
humanReadable = "transaction";
action = "transaction";
}
}
if (DateUtils.isAfter(lastTransactionDate, transactionDate)) {
String errorMessage = "The date on which the " + humanReadable + " is made cannot be earlier than last transaction date";
String postfix = "cannot.be.made.before.last.transaction.date";
throw new InvalidLoanStateTransitionException(action, postfix, errorMessage, lastTransactionDate);
}
}
} }
@@ -85,7 +85,6 @@ import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundExceptio
import org.apache.fineract.portfolio.charge.exception.LoanChargeWaiveCannotBeReversedException; import org.apache.fineract.portfolio.charge.exception.LoanChargeWaiveCannotBeReversedException;
import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
@@ -98,7 +97,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
@@ -727,21 +725,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
paymentDetail); paymentDetail);
// Update loan transaction on repayment. // Update loan transaction on repayment.
if (loan.getLoanType().isIndividualAccount()) { loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, loanTransaction);
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
loanCollateralManagement.setLoanTransactionData(loanTransaction);
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
if (loan.getStatus().isClosed()) {
loanCollateralManagement.setIsReleased(true);
BigDecimal quantity = loanCollateralManagement.getQuantity();
clientCollateralManagement.updateQuantity(clientCollateralManagement.getQuantity().add(quantity));
loanCollateralManagement.setClientCollateralManagement(clientCollateralManagement);
}
}
this.loanAccountDomainService.updateLoanCollateralTransaction(loanCollateralManagements);
}
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
@@ -40,6 +40,7 @@ import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException; import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException;
import org.apache.fineract.cob.service.LoanAccountLockService; import org.apache.fineract.cob.service.LoanAccountLockService;
@@ -134,7 +135,6 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarType;
import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException; import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.apache.fineract.portfolio.collectionsheet.command.SingleDisbursalCommand; import org.apache.fineract.portfolio.collectionsheet.command.SingleDisbursalCommand;
@@ -151,7 +151,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonit
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
@@ -170,7 +169,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
@@ -202,7 +200,6 @@ import org.apache.fineract.portfolio.note.domain.Note;
import org.apache.fineract.portfolio.note.domain.NoteRepository; import org.apache.fineract.portfolio.note.domain.NoteRepository;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.data.PostDatedChecksStatus;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler;
@@ -477,11 +474,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.updateLoanScheduleDependentDerivedFields(); loan.updateLoanScheduleDependentDerivedFields();
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
final String noteText = command.stringValueOfParameterNamed("note"); createNote(loan, command, changes);
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
// auto create standing instruction // auto create standing instruction
createStandingInstruction(loan); createStandingInstruction(loan);
@@ -550,6 +543,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build(); .build();
} }
private void createNote(Loan loan, JsonCommand command, Map<String, Object> changes) {
final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText);
final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
}
private ChangedTransactionDetail disburseLoan(JsonCommand command, boolean isPaymentTypeApplicableForDisbursementCharge, private ChangedTransactionDetail disburseLoan(JsonCommand command, boolean isPaymentTypeApplicableForDisbursementCharge,
PaymentDetail paymentDetail, Loan loan, AppUser currentUser, Map<String, Object> changes, PaymentDetail paymentDetail, Loan loan, AppUser currentUser, Map<String, Object> changes,
ScheduleGeneratorDTO scheduleGeneratorDTO) { ScheduleGeneratorDTO scheduleGeneratorDTO) {
@@ -813,11 +815,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
final String noteText = command.stringValueOfParameterNamed("note"); createNote(loan, command, changes);
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings() for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings()
.entrySet()) { .entrySet()) {
@@ -929,13 +927,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN); this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN);
String noteText; String noteText;
if (command.hasParameter("note")) { createNote(loan, command, changes);
noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
}
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds,
existingReversedTransactionIds, isAccountTransfer); existingReversedTransactionIds, isAccountTransfer);
@@ -1043,7 +1035,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return statusChanged; return statusChanged;
} }
private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { private void updateLoanTransferTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
if (transferTransaction != null) { if (transferTransaction != null) {
transferTransaction.updateToLoanTransaction(newLoanTransaction); transferTransaction.updateToLoanTransaction(newLoanTransaction);
@@ -1052,7 +1044,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
// TODO check // TODO check
public boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction, public boolean isChronologicallyTheLatestTransaction(final LoanTransaction loanTransaction,
final List<LoanTransaction> loanTransactions) { final List<LoanTransaction> loanTransactions) {
boolean isChronologicallyLatestRepaymentOrWaiver = true; boolean isChronologicallyLatestRepaymentOrWaiver = true;
@@ -1082,8 +1074,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
newInterestPaymentWaiverTransaction.updateLoan(loan); newInterestPaymentWaiverTransaction.updateLoan(loan);
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction, final boolean isTransactionChronologicallyLatest = loan
loan.getLoanTransactions()); .isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction);
if (newInterestPaymentWaiverTransaction.isNotZero(loan.getLoanRepaymentScheduleDetail().getCurrency())) { if (newInterestPaymentWaiverTransaction.isNotZero(loan.getLoanRepaymentScheduleDetail().getCurrency())) {
loan.addLoanTransaction(newInterestPaymentWaiverTransaction); loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
@@ -1093,7 +1085,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.determineProcessor(loan.getTransactionProcessingStrategyCode()); .determineProcessor(loan.getTransactionProcessingStrategyCode());
final LoanRepaymentScheduleInstallment currentInstallment = loan final LoanRepaymentScheduleInstallment currentInstallment = loan
.fetchLoanRepaymentScheduleInstallment(newInterestPaymentWaiverTransaction.getTransactionDate()); .fetchLoanRepaymentScheduleInstallmentByDueDate(newInterestPaymentWaiverTransaction.getTransactionDate());
boolean reprocess = true; boolean reprocess = true;
@@ -1254,8 +1246,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// disable all active standing orders linked to this loan if status changes to closed // disable all active standing orders linked to this loan if status changes to closed
loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan); loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
updateAndSavePostDatedChecksForIndividualAccount(loan, newInterestPaymentWaiverTransaction); loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, newInterestPaymentWaiverTransaction);
updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, newInterestPaymentWaiverTransaction); loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, newInterestPaymentWaiverTransaction);
// Finished Notification // Finished Notification
@@ -1286,62 +1278,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue()); loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
// Trigger transaction replayed event // Trigger transaction replayed event
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail); replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
} }
} }
private void updateAndSavePostDatedChecksForIndividualAccount(Loan loan, LoanTransaction loanTransaction) {
if (loan.getLoanType().isIndividualAccount()) {
// Mark Post Dated Check as paid.
final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = loanTransaction
.getLoanTransactionToRepaymentScheduleMappings();
if (loanTransactionToRepaymentScheduleMappings != null) {
for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : loanTransactionToRepaymentScheduleMappings) {
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping
.getLoanRepaymentScheduleInstallment();
if (loanRepaymentScheduleInstallment != null) {
final boolean isPaid = loanRepaymentScheduleInstallment.isNotFullyPaidOff();
PostDatedChecks postDatedChecks = this.postDatedChecksRepository
.getPendingPostDatedCheck(loanRepaymentScheduleInstallment);
if (postDatedChecks != null) {
if (!isPaid) {
postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PAID);
} else {
postDatedChecks.setStatus(PostDatedChecksStatus.POST_DATED_CHECKS_PENDING);
}
this.postDatedChecksRepository.saveAndFlush(postDatedChecks);
} else {
break;
}
}
}
}
}
}
private void updateAndSaveLoanCollateralTransactionsForIndividualAccounts(Loan loan, LoanTransaction loanTransaction) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
loanCollateralManagement.setLoanTransactionData(loanTransaction);
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
if (loan.getStatus().isClosed()) {
loanCollateralManagement.setIsReleased(true);
BigDecimal quantity = loanCollateralManagement.getQuantity();
clientCollateralManagement.updateQuantity(clientCollateralManagement.getQuantity().add(quantity));
loanCollateralManagement.setClientCollateralManagement(clientCollateralManagement);
}
}
this.loanAccountDomainService.updateLoanCollateralTransaction(loanCollateralManagements);
}
}
@Transactional @Transactional
@Override @Override
public CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(final LoanTransactionType repaymentTransactionType, public CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(final LoanTransactionType repaymentTransactionType,
@@ -1378,22 +1321,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
transactionAmount, paymentDetail, noteText, txnExternalId, isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer, transactionAmount, paymentDetail, noteText, txnExternalId, isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer,
holidayDetailDto, isHolidayValidationDone); holidayDetailDto, isHolidayValidationDone);
loan = loanTransaction.getLoan(); loan = loanTransaction.getLoan();
// Update loan transaction on repayment. this.loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, loanTransaction);
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
loanCollateralManagement.setLoanTransactionData(loanTransaction);
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
if (loan.getStatus().isClosed()) {
loanCollateralManagement.setIsReleased(true);
BigDecimal quantity = loanCollateralManagement.getQuantity();
clientCollateralManagement.updateQuantity(clientCollateralManagement.getQuantity().add(quantity));
loanCollateralManagement.setClientCollateralManagement(clientCollateralManagement);
}
}
this.loanAccountDomainService.updateLoanCollateralTransaction(loanCollateralManagements);
}
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) // return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
.withLoanId(loan.getId()) // .withLoanId(loan.getId()) //
@@ -2018,20 +1946,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPostBusinessEvent(new LoanCloseBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanCloseBusinessEvent(loan));
// Update loan transaction on repayment. // Update loan transaction on repayment.
if (loan.getLoanType().isIndividualAccount()) { this.loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, null);
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
if (loan.getStatus().isClosed()) {
loanCollateralManagement.setIsReleased(true);
BigDecimal quantity = loanCollateralManagement.getQuantity();
clientCollateralManagement.updateQuantity(clientCollateralManagement.getQuantity().add(quantity));
loanCollateralManagement.setClientCollateralManagement(clientCollateralManagement);
}
}
this.loanAccountDomainService.updateLoanCollateralTransaction(loanCollateralManagements);
}
// disable all active standing instructions linked to the loan // disable all active standing instructions linked to the loan
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan); this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
@@ -2099,20 +2014,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan); this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
// Update loan transaction on repayment. // Update loan transaction on repayment.
if (loan.getLoanType().isIndividualAccount()) { this.loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, null);
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
if (loan.getStatus().isClosed()) {
loanCollateralManagement.setIsReleased(true);
BigDecimal quantity = loanCollateralManagement.getQuantity();
clientCollateralManagement.updateQuantity(clientCollateralManagement.getQuantity().add(quantity));
loanCollateralManagement.setClientCollateralManagement(clientCollateralManagement);
}
}
this.loanAccountDomainService.updateLoanCollateralTransaction(loanCollateralManagements);
}
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -3157,14 +3059,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
String noteText; createNote(loan, command, changes);
if (command.hasParameter("note")) {
noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) {
final Note note = Note.loanNote(loan, noteText);
this.noteRepository.save(note);
}
}
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds, final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds,
existingReversedTransactionIds, isAccountTransfer); existingReversedTransactionIds, isAccountTransfer);
@@ -3351,6 +3246,87 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build(); .build();
} }
@Override
public CommandProcessingResult makeRefund(final Long loanId, final LoanTransactionType loanTransactionType, final JsonCommand command) {
// API rule validations
this.loanTransactionValidator.validateRefund(command.json());
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
changes.put(LoanApiConstants.externalIdParameterName, txnExternalId);
Loan loan = this.loanAssembler.assembleFrom(loanId);
// Build loan schedule generator dto
LocalDate recalculateFrom = loan.repaymentScheduleDetail().isInterestRecalculationEnabled() ? transactionDate : null;
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, null);
// Domain rule validations
this.loanTransactionValidator.validateRefund(loan, loanTransactionType, transactionDate, scheduleGeneratorDTO);
// Create payment details
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
// Create note
createNote(loan, command, changes);
// Initial transaction ids for journal entry generation
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
final List<Long> existingReversedTransactionIds = loan.findExistingReversedTransactionIds();
// Create refund transaction(s)
Pair<LoanTransaction, LoanTransaction> refundTransactions = loanAccountDomainService.makeRefund(loan, scheduleGeneratorDTO,
loanTransactionType, transactionDate, transactionAmount, paymentDetail, txnExternalId);
LoanTransaction refundTransaction = refundTransactions.getLeft();
LoanTransaction interestRefundTransaction = refundTransactions.getRight();
// Accrual reprocessing
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanAccrualsProcessingService.processIncomePostingAndAccruals(loan);
}
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
// changes to closed
loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
//// Mark Post Dated Check as paid., Do we need this for refund?
loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, refundTransaction);
if (interestRefundTransaction != null) {
loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, refundTransaction);
}
// Collateral management
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, refundTransaction);
if (interestRefundTransaction != null) {
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, refundTransaction);
}
// Raise business events
loanAccrualTransactionBusinessEventService.raiseBusinessEventForAccrualTransactions(loan, existingTransactionIds);
loanAccrualsProcessingService.processAccrualsForInterestRecalculation(loan,
loan.repaymentScheduleDetail().isInterestRecalculationEnabled());
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
Long entityId = refundTransaction.getId();
ExternalId entityExternalId = refundTransaction.getExternalId();
Long subEntityId = interestRefundTransaction != null ? interestRefundTransaction.getId() : null;
ExternalId subEntityExternalId = interestRefundTransaction != null ? interestRefundTransaction.getExternalId() : null;
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
.withLoanId(loan.getId()) //
.withEntityId(entityId) //
.withEntityExternalId(entityExternalId) //
.withSubEntityId(subEntityId)//
.withSubEntityExternalId(subEntityExternalId)//
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.with(changes) //
.build();
}
private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) { private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) {
if (!loan.isMultiDisburmentLoan()) { if (!loan.isMultiDisburmentLoan()) {
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal"; final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";