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);
|
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();
|
||||||
|
|||||||
+9
-2
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -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
|
||||||
|
|||||||
+1
-13
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-13
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+164
-38
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+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.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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.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)) {
|
||||||
|
|||||||
+106
-130
@@ -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";
|
||||||
|
|||||||
Reference in New Issue
Block a user