FINERACT-2107: Extract Merchant issued refund and Payout refund handling
This commit is contained in:
+5
-7
@@ -2352,7 +2352,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
loanTransaction.updateLoan(this);
|
||||
|
||||
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction, getLoanTransactions());
|
||||
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction);
|
||||
|
||||
if (loanTransaction.isNotZero(loanCurrency())) {
|
||||
addLoanTransaction(loanTransaction);
|
||||
@@ -2396,7 +2396,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment(
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallmentByDueDate(
|
||||
loanTransaction.getTransactionDate());
|
||||
|
||||
boolean reprocess = isForeclosure() || !isTransactionChronologicallyLatest || adjustedTransaction != null
|
||||
@@ -2471,7 +2471,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return loanTransactionDate;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallment(LocalDate dueDate) {
|
||||
public LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallmentByDueDate(LocalDate dueDate) {
|
||||
return getRepaymentScheduleInstallments().stream() //
|
||||
.filter(installment -> dueDate.equals(installment.getDueDate())).findFirst() //
|
||||
.orElse(null);
|
||||
@@ -2504,8 +2504,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean doPostLoanTransactionChecks(final LocalDate transactionDate,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||
public boolean doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||
boolean statusChanged = false;
|
||||
boolean isOverpaid = MathUtil.isGreaterThanZero(totalOverpaid);
|
||||
if (isOverpaid) {
|
||||
@@ -2549,8 +2548,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
this.actualMaturityDate = null;
|
||||
}
|
||||
|
||||
private boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction,
|
||||
final List<LoanTransaction> loanTransactions) {
|
||||
public boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction) {
|
||||
boolean isChronologicallyLatestRepaymentOrWaiver = true;
|
||||
|
||||
final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
|
||||
|
||||
+9
-2
@@ -23,11 +23,13 @@ import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.domain.ExternalId;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyActionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
|
||||
public interface LoanAccountDomainService {
|
||||
@@ -54,8 +56,6 @@ public interface LoanAccountDomainService {
|
||||
LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
|
||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId);
|
||||
|
||||
void updateLoanCollateralTransaction(Set<LoanCollateralManagement> loanCollateralManagementList);
|
||||
|
||||
void updateLoanCollateralStatus(Set<LoanCollateralManagement> loanCollateralManagementSet, boolean isReleased);
|
||||
|
||||
/**
|
||||
@@ -94,6 +94,13 @@ public interface LoanAccountDomainService {
|
||||
LoanTransaction creditBalanceRefund(Loan loan, LocalDate transactionDate, BigDecimal transactionAmount, String noteText,
|
||||
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);
|
||||
|
||||
void updateAndSaveLoanCollateralTransactionsForIndividualAccounts(Loan loan, LoanTransaction transaction);
|
||||
}
|
||||
|
||||
+6
@@ -206,6 +206,12 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
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) {
|
||||
for (LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping : this.loanTransactionToRepaymentScheduleMappings) {
|
||||
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanTransactionToRepaymentScheduleMapping
|
||||
|
||||
+1
-13
@@ -21,13 +21,10 @@ package org.apache.fineract.portfolio.loanaccount.handler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
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.transaction.annotation.Transactional;
|
||||
|
||||
@@ -37,19 +34,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class LoanMerchantIssuedRefundCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final LoanWritePlatformService writePlatformService;
|
||||
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
try {
|
||||
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();
|
||||
}
|
||||
return this.writePlatformService.makeRefund(command.getLoanId(), LoanTransactionType.MERCHANT_ISSUED_REFUND, command);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-13
@@ -21,13 +21,10 @@ package org.apache.fineract.portfolio.loanaccount.handler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
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.transaction.annotation.Transactional;
|
||||
|
||||
@@ -37,19 +34,10 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class LoanPayoutRefundCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final LoanWritePlatformService writePlatformService;
|
||||
private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
try {
|
||||
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();
|
||||
}
|
||||
return this.writePlatformService.makeRefund(command.getLoanId(), LoanTransactionType.PAYOUT_REFUND, command);
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -134,4 +134,6 @@ public interface LoanWritePlatformService {
|
||||
|
||||
@Transactional
|
||||
CommandProcessingResult undoChargeOff(JsonCommand command);
|
||||
|
||||
CommandProcessingResult makeRefund(Long loanId, LoanTransactionType loanTransactionType, JsonCommand command);
|
||||
}
|
||||
|
||||
+164
-38
@@ -28,6 +28,7 @@ import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
@@ -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.client.domain.Client;
|
||||
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.helper.DelinquencyEffectivePauseHelper;
|
||||
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.LoanScheduleDelinquencyData;
|
||||
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.InterestRefundServiceDelegate;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
||||
@@ -139,6 +144,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||
private final InterestRefundServiceDelegate interestRefundServiceDelegate;
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -150,12 +156,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone, false);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void updateLoanCollateralTransaction(Set<LoanCollateralManagement> loanCollateralManagementSet) {
|
||||
this.loanCollateralManagementRepository.saveAll(loanCollateralManagementSet);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void updateLoanCollateralStatus(Set<LoanCollateralManagement> loanCollateralManagementSet, boolean isReleased) {
|
||||
@@ -165,8 +165,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.loanCollateralManagementRepository.saveAll(loanCollateralManagementSet);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public LoanTransaction createInterestRefundLoanTransaction(Loan loan, final LocalDate transactionDate,
|
||||
private LoanTransaction createInterestRefundLoanTransaction(Loan loan, final LocalDate transactionDate,
|
||||
BigDecimal relatedRefundTransactionAmount) {
|
||||
InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan);
|
||||
if (interestRefundService == null) {
|
||||
@@ -174,15 +173,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
BigDecimal interestRefundAmount = interestRefundService.calculateInterestRefundAmount(loan.getId(), relatedRefundTransactionAmount,
|
||||
transactionDate);
|
||||
|
||||
final ExternalId txnExternalId = externalIdFactory.create();
|
||||
|
||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan));
|
||||
|
||||
LoanTransaction newInterestRefundTransaction;
|
||||
newInterestRefundTransaction = LoanTransaction.interestRefund(loan, interestRefundAmount, transactionDate, txnExternalId);
|
||||
|
||||
return newInterestRefundTransaction;
|
||||
return LoanTransaction.interestRefund(loan, interestRefundAmount, transactionDate, txnExternalId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -225,18 +218,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom,
|
||||
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,
|
||||
defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
|
||||
scheduleGeneratorDTO, isHolidayValidationDone);
|
||||
@@ -247,9 +228,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
|
||||
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
|
||||
if (newInterestRefundTransaction != null) {
|
||||
saveLoanTransactionWithDataIntegrityViolationChecks(newInterestRefundTransaction);
|
||||
}
|
||||
|
||||
/***
|
||||
* 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) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
}
|
||||
// Trigger transaction replayed event
|
||||
replayedTransactionBusinessEventService.raiseTransactionReplayedEvents(changedTransactionDetail);
|
||||
@@ -316,10 +294,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldCreateInterestRefundTransaction && newInterestRefundTransaction != null) {
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanTransactionInterestRefundPostBusinessEvent(newInterestRefundTransaction));
|
||||
}
|
||||
|
||||
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);
|
||||
if (transferTransaction != null) {
|
||||
transferTransaction.updateToLoanTransaction(newLoanTransaction);
|
||||
@@ -810,7 +785,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
if (changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
}
|
||||
// Trigger transaction replayed event
|
||||
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
|
||||
public LoanTransaction applyInterestRefund(final Loan loan, final LoanRefundRequestData loanRefundRequest) {
|
||||
final PaymentDetail paymentDetail = null;
|
||||
@@ -868,5 +995,4 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
|
||||
return interestRefundTransaction;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+91
-16
@@ -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.CalendarInstanceRepository;
|
||||
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.exception.ClientNotActiveException;
|
||||
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.LoanRepositoryWrapper;
|
||||
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.InvalidLoanStateTransitionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||
@@ -410,7 +410,10 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
|
||||
public void validateNewRepaymentTransaction(final String json) {
|
||||
validatePaymentTransaction(json);
|
||||
}
|
||||
|
||||
private void validatePaymentTransaction(String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
@@ -439,21 +442,6 @@ public final class LoanTransactionValidator {
|
||||
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) {
|
||||
// Validate all string payment detail fields for max length
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-17
@@ -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.client.domain.Client;
|
||||
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.group.domain.Group;
|
||||
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.LoanChargePaidBy;
|
||||
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.LoanEvent;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
|
||||
@@ -727,21 +725,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
paymentDetail);
|
||||
|
||||
// Update loan transaction on repayment.
|
||||
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);
|
||||
}
|
||||
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, loanTransaction);
|
||||
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
|
||||
+106
-130
@@ -40,6 +40,7 @@ import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.cob.exceptions.LoanAccountLockCannotBeOverruledException;
|
||||
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.client.domain.Client;
|
||||
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.CollectionSheetBulkRepaymentCommand;
|
||||
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.LoanAccountDomainService;
|
||||
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.LoanEvent;
|
||||
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.LoanTransactionRelationTypeEnum;
|
||||
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.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
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.paymentdetail.domain.PaymentDetail;
|
||||
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.PostDatedChecksRepository;
|
||||
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler;
|
||||
@@ -477,11 +474,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loan.updateLoanScheduleDependentDerivedFields();
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
final Note note = Note.loanNote(loan, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
createNote(loan, command, changes);
|
||||
// auto create standing instruction
|
||||
createStandingInstruction(loan);
|
||||
|
||||
@@ -550,6 +543,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.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,
|
||||
PaymentDetail paymentDetail, Loan loan, AppUser currentUser, Map<String, Object> changes,
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
@@ -813,11 +815,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
final Note note = Note.loanNote(loan, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
createNote(loan, command, changes);
|
||||
if (changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings()
|
||||
.entrySet()) {
|
||||
@@ -929,13 +927,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
this.accountTransfersWritePlatformService.reverseAllTransactions(loanId, PortfolioAccountType.LOAN);
|
||||
String noteText;
|
||||
if (command.hasParameter("note")) {
|
||||
noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
final Note note = Note.loanNote(loan, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
}
|
||||
createNote(loan, command, changes);
|
||||
boolean isAccountTransfer = false;
|
||||
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds,
|
||||
existingReversedTransactionIds, isAccountTransfer);
|
||||
@@ -1043,7 +1035,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
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);
|
||||
if (transferTransaction != null) {
|
||||
transferTransaction.updateToLoanTransaction(newLoanTransaction);
|
||||
@@ -1052,7 +1044,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
|
||||
// TODO check
|
||||
public boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction,
|
||||
public boolean isChronologicallyTheLatestTransaction(final LoanTransaction loanTransaction,
|
||||
final List<LoanTransaction> loanTransactions) {
|
||||
boolean isChronologicallyLatestRepaymentOrWaiver = true;
|
||||
|
||||
@@ -1082,8 +1074,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
newInterestPaymentWaiverTransaction.updateLoan(loan);
|
||||
|
||||
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction,
|
||||
loan.getLoanTransactions());
|
||||
final boolean isTransactionChronologicallyLatest = loan
|
||||
.isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction);
|
||||
|
||||
if (newInterestPaymentWaiverTransaction.isNotZero(loan.getLoanRepaymentScheduleDetail().getCurrency())) {
|
||||
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
|
||||
@@ -1093,7 +1085,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.determineProcessor(loan.getTransactionProcessingStrategyCode());
|
||||
|
||||
final LoanRepaymentScheduleInstallment currentInstallment = loan
|
||||
.fetchLoanRepaymentScheduleInstallment(newInterestPaymentWaiverTransaction.getTransactionDate());
|
||||
.fetchLoanRepaymentScheduleInstallmentByDueDate(newInterestPaymentWaiverTransaction.getTransactionDate());
|
||||
|
||||
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
|
||||
loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
|
||||
|
||||
updateAndSavePostDatedChecksForIndividualAccount(loan, newInterestPaymentWaiverTransaction);
|
||||
updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, newInterestPaymentWaiverTransaction);
|
||||
loanAccountDomainService.updateAndSavePostDatedChecksForIndividualAccount(loan, newInterestPaymentWaiverTransaction);
|
||||
loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, newInterestPaymentWaiverTransaction);
|
||||
|
||||
// Finished Notification
|
||||
|
||||
@@ -1286,62 +1278,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (changedTransactionDetail != null) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(mapEntry.getValue());
|
||||
updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
updateLoanTransferTransaction(mapEntry.getKey(), mapEntry.getValue());
|
||||
}
|
||||
// Trigger transaction replayed event
|
||||
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
|
||||
@Override
|
||||
public CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(final LoanTransactionType repaymentTransactionType,
|
||||
@@ -1378,22 +1321,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
transactionAmount, paymentDetail, noteText, txnExternalId, isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer,
|
||||
holidayDetailDto, isHolidayValidationDone);
|
||||
loan = loanTransaction.getLoan();
|
||||
// Update loan transaction on repayment.
|
||||
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);
|
||||
}
|
||||
this.loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, loanTransaction);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
||||
.withLoanId(loan.getId()) //
|
||||
@@ -2018,20 +1946,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanCloseBusinessEvent(loan));
|
||||
|
||||
// Update loan transaction on repayment.
|
||||
if (loan.getLoanType().isIndividualAccount()) {
|
||||
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);
|
||||
}
|
||||
this.loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, null);
|
||||
|
||||
// disable all active standing instructions linked to the loan
|
||||
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
|
||||
@@ -2099,20 +2014,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
|
||||
|
||||
// Update loan transaction on repayment.
|
||||
if (loan.getLoanType().isIndividualAccount()) {
|
||||
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);
|
||||
}
|
||||
this.loanAccountDomainService.updateAndSaveLoanCollateralTransactionsForIndividualAccounts(loan, null);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
@@ -3157,14 +3059,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
String noteText;
|
||||
if (command.hasParameter("note")) {
|
||||
noteText = command.stringValueOfParameterNamed("note");
|
||||
if (StringUtils.isNotBlank(noteText)) {
|
||||
final Note note = Note.loanNote(loan, noteText);
|
||||
this.noteRepository.save(note);
|
||||
}
|
||||
}
|
||||
createNote(loan, command, changes);
|
||||
boolean isAccountTransfer = false;
|
||||
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.getCode(), existingTransactionIds,
|
||||
existingReversedTransactionIds, isAccountTransfer);
|
||||
@@ -3351,6 +3246,87 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.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) {
|
||||
if (!loan.isMultiDisburmentLoan()) {
|
||||
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
|
||||
|
||||
Reference in New Issue
Block a user