FINERACT-1926: Asset externalisation logic enhancements and bugfixes
This commit is contained in:
committed by
Arnold Gálovics
parent
b1af8a3f83
commit
9698b6d5b3
+26
-29
@@ -53,36 +53,27 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
List<ExternalAssetOwnerTransfer> transferDataList = externalAssetOwnerTransferRepository.findAll(
|
||||
(root, query, criteriaBuilder) -> criteriaBuilder.and(criteriaBuilder.equal(root.get("loanId"), loanId),
|
||||
criteriaBuilder.equal(root.get("settlementDate"), settlementDate),
|
||||
root.get("status").in(
|
||||
List.of(ExternalTransferStatus.PENDING, ExternalTransferStatus.ACTIVE, ExternalTransferStatus.BUYBACK))),
|
||||
root.get("status").in(List.of(ExternalTransferStatus.PENDING, ExternalTransferStatus.BUYBACK))),
|
||||
Sort.by(Sort.Direction.ASC, "id"));
|
||||
int size = transferDataList.size();
|
||||
|
||||
if (size > 2) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Found too many owner transfers(%s) by the settlement date(%s)", size, settlementDate));
|
||||
} else if (size == 2) {
|
||||
if (size == 2) {
|
||||
ExternalTransferStatus firstTransferStatus = transferDataList.get(0).getStatus();
|
||||
ExternalTransferStatus secondTransferStatus = transferDataList.get(1).getStatus();
|
||||
|
||||
if (!ExternalTransferStatus.BUYBACK.equals(secondTransferStatus)) {
|
||||
throw new IllegalStateException(String.format("Illegal transfer found. Expected %s, found: %s",
|
||||
ExternalTransferStatus.BUYBACK, secondTransferStatus));
|
||||
}
|
||||
|
||||
switch (firstTransferStatus) {
|
||||
case PENDING -> handleSameDaySaleAndBuyback(settlementDate, transferDataList);
|
||||
case ACTIVE -> handleBuyback(loan, settlementDate, transferDataList);
|
||||
default -> throw new IllegalStateException(String.format("Illegal transfer found. Expected %s or %s, found: %s",
|
||||
ExternalTransferStatus.PENDING, ExternalTransferStatus.ACTIVE, firstTransferStatus));
|
||||
if (!ExternalTransferStatus.PENDING.equals(firstTransferStatus)
|
||||
|| !ExternalTransferStatus.BUYBACK.equals(secondTransferStatus)) {
|
||||
throw new IllegalStateException(String.format("Illegal transfer found. Expected %s and %s, found: %s and %s",
|
||||
ExternalTransferStatus.PENDING, ExternalTransferStatus.BUYBACK, firstTransferStatus, secondTransferStatus));
|
||||
}
|
||||
handleSameDaySaleAndBuyback(settlementDate, transferDataList);
|
||||
} else if (size == 1) {
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = transferDataList.get(0);
|
||||
if (!ExternalTransferStatus.PENDING.equals(externalAssetOwnerTransfer.getStatus())) {
|
||||
throw new IllegalStateException(String.format("Illegal transfer found. Expected %s, found: %s",
|
||||
ExternalTransferStatus.PENDING, externalAssetOwnerTransfer.getStatus()));
|
||||
ExternalAssetOwnerTransfer transfer = transferDataList.get(0);
|
||||
if (ExternalTransferStatus.PENDING.equals(transfer.getStatus())) {
|
||||
handleSale(loan, settlementDate, transfer);
|
||||
} else if (ExternalTransferStatus.BUYBACK.equals(transfer.getStatus())) {
|
||||
handleBuyback(loan, settlementDate, transfer);
|
||||
}
|
||||
handleSale(loan, settlementDate, externalAssetOwnerTransfer);
|
||||
}
|
||||
|
||||
log.debug("end processing loan ownership transfer business step for loan Id [{}]", loan.getId());
|
||||
@@ -95,20 +86,25 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
}
|
||||
|
||||
private void handleBuyback(final Loan loan, final LocalDate settlementDate,
|
||||
final List<ExternalAssetOwnerTransfer> externalAssetOwnerTransferList) {
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = buybackAsset(loan, settlementDate, externalAssetOwnerTransferList);
|
||||
final ExternalAssetOwnerTransfer buybackExternalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransfer activeExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository
|
||||
.findOne((root, query, criteriaBuilder) -> criteriaBuilder.and(criteriaBuilder.equal(root.get("loanId"), loan.getId()),
|
||||
criteriaBuilder.equal(root.get("ownerId"), buybackExternalAssetOwnerTransfer.getOwnerId()),
|
||||
criteriaBuilder.equal(root.get("status"), ExternalTransferStatus.ACTIVE),
|
||||
criteriaBuilder.equal(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31)))
|
||||
.orElseThrow();
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = buybackAsset(loan, settlementDate, buybackExternalAssetOwnerTransfer,
|
||||
activeExternalAssetOwnerTransfer);
|
||||
// TODO: trigger asset loan transfer executed event
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer buybackAsset(final Loan loan, final LocalDate settlementDate,
|
||||
List<ExternalAssetOwnerTransfer> externalAssetOwnerTransferList) {
|
||||
ExternalAssetOwnerTransfer saleExternalAssetOwnerTransfer = externalAssetOwnerTransferList.get(0);
|
||||
ExternalAssetOwnerTransfer buybackExternalAssetOwnerTransfer = externalAssetOwnerTransferList.get(1);
|
||||
saleExternalAssetOwnerTransfer.setEffectiveDateTo(settlementDate);
|
||||
ExternalAssetOwnerTransfer buybackExternalAssetOwnerTransfer, ExternalAssetOwnerTransfer activeExternalAssetOwnerTransfer) {
|
||||
activeExternalAssetOwnerTransfer.setEffectiveDateTo(settlementDate);
|
||||
buybackExternalAssetOwnerTransfer.setEffectiveDateTo(buybackExternalAssetOwnerTransfer.getEffectiveDateFrom());
|
||||
externalAssetOwnerTransferRepository.save(saleExternalAssetOwnerTransfer);
|
||||
externalAssetOwnerTransferRepository.save(activeExternalAssetOwnerTransfer);
|
||||
buybackExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository.save(buybackExternalAssetOwnerTransfer);
|
||||
externalAssetOwnerTransferLoanMappingRepository.deleteByLoanIdAndOwnerTransfer(loan.getId(), buybackExternalAssetOwnerTransfer);
|
||||
externalAssetOwnerTransferLoanMappingRepository.deleteByLoanIdAndOwnerTransfer(loan.getId(), activeExternalAssetOwnerTransfer);
|
||||
// TODO: create asset ownership accounting entries
|
||||
// TODO: create asset ownership transaction entries
|
||||
return buybackExternalAssetOwnerTransfer;
|
||||
@@ -161,6 +157,7 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
final ExternalTransferSubStatus subStatus, final LocalDate effectiveDateFrom, final LocalDate effectiveDateTo) {
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
newExternalAssetOwnerTransfer.setOwner(externalAssetOwnerTransfer.getOwner());
|
||||
newExternalAssetOwnerTransfer.setOwnerId(externalAssetOwnerTransfer.getOwnerId());
|
||||
newExternalAssetOwnerTransfer.setExternalId(externalAssetOwnerTransfer.getExternalId());
|
||||
newExternalAssetOwnerTransfer.setStatus(status);
|
||||
newExternalAssetOwnerTransfer.setSubStatus(subStatus);
|
||||
|
||||
+4
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.investor.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -39,4 +41,6 @@ public interface ExternalAssetOwnerTransferRepository
|
||||
@Query("select e from ExternalAssetOwnerTransfer e where e.loanId = :loanId and e.id = (select max(ex.id) from ExternalAssetOwnerTransfer ex where ex.loanId = :loanId)")
|
||||
Optional<ExternalAssetOwnerTransfer> findLatestByLoanId(@Param("loanId") Long loanId);
|
||||
|
||||
@Query("SELECT t FROM ExternalAssetOwnerTransfer t WHERE t.loanId = :loanId AND t.effectiveDateTo > :effectiveDate")
|
||||
List<ExternalAssetOwnerTransfer> findEffectiveTransfers(@Param("loanId") Long loanId, @Param("effectiveDate") LocalDate effectiveDate);
|
||||
}
|
||||
|
||||
+104
-52
@@ -33,16 +33,17 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndStatus;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.data.LoanIdAndExternalIdData;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.investor.data.ExternalTransferRequestParameters;
|
||||
@@ -53,7 +54,9 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
|
||||
import org.apache.fineract.investor.exception.ExternalAssetOwnerInitiateTransferException;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformServiceCommon;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -61,36 +64,96 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@RequiredArgsConstructor
|
||||
public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersWriteService {
|
||||
|
||||
private static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||
private static final List<LoanStatus> NON_CLOSED_LOAN_STATUSES = List.of(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, LoanStatus.APPROVED,
|
||||
LoanStatus.ACTIVE, LoanStatus.TRANSFER_IN_PROGRESS, LoanStatus.TRANSFER_ON_HOLD);
|
||||
private static final List<ExternalTransferStatus> BUYBACK_READY_STATUSES = List.of(ExternalTransferStatus.PENDING,
|
||||
ExternalTransferStatus.ACTIVE);
|
||||
private final ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
private final ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
|
||||
private final ExternalAssetOwnerRepository externalAssetOwnerRepository;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanReadPlatformServiceCommon loanReadPlatformService;
|
||||
private final LoanRepository loanRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult saleLoanByLoanId(JsonCommand command) {
|
||||
final JsonElement json = fromApiJsonHelper.parse(command.json());
|
||||
Long loanId = command.getLoanId();
|
||||
LoanIdAndExternalIdData loanIdAndExternalId = loanReadPlatformService.getTransferableLoanIdAndExternalId(loanId);
|
||||
validateLoanStatus(loanIdAndExternalId);
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = parseJson(loanId, command.json(), loanIdAndExternalId.getLoanExternalId(),
|
||||
ExternalTransferStatus.PENDING);
|
||||
LocalDate settlementDate = getSettlementDateFromJson(json);
|
||||
LoanIdAndExternalIdAndStatus loanIdAndExternalIdAndStatus = fetchLoanDetails(loanId);
|
||||
validateLoanStatus(loanIdAndExternalIdAndStatus);
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = createSaleTransfer(loanId, command.json(),
|
||||
loanIdAndExternalIdAndStatus.getExternalId());
|
||||
validateSale(externalAssetOwnerTransfer);
|
||||
ExternalAssetOwnerTransfer savedExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer);
|
||||
return buildResponseData(savedExternalAssetOwnerTransfer);
|
||||
externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer);
|
||||
return buildResponseData(externalAssetOwnerTransfer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult buybackLoanByLoanId(JsonCommand command) {
|
||||
final JsonElement json = fromApiJsonHelper.parse(command.json());
|
||||
Long loanId = command.getLoanId();
|
||||
LoanIdAndExternalIdData loanIdAndExternalId = loanReadPlatformService.getTransferableLoanIdAndExternalId(loanId);
|
||||
validateLoanStatus(loanIdAndExternalId);
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = parseJson(loanId, command.json(), loanIdAndExternalId.getLoanExternalId(),
|
||||
ExternalTransferStatus.BUYBACK);
|
||||
validateBuyBack(externalAssetOwnerTransfer);
|
||||
ExternalAssetOwnerTransfer savedExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer);
|
||||
return buildResponseData(savedExternalAssetOwnerTransfer);
|
||||
LocalDate settlementDate = getSettlementDateFromJson(json);
|
||||
ExternalId externalId = getTransferExternalIdFromJson(json);
|
||||
LoanIdAndExternalIdAndStatus loanIdAndExternalIdAndStatus = fetchLoanDetails(loanId);
|
||||
validateLoanStatus(loanIdAndExternalIdAndStatus);
|
||||
validateSettlementDate(settlementDate);
|
||||
ExternalAssetOwnerTransfer effectiveTransfer = fetchAndValidateEffectiveTransferForBuyback(loanId, settlementDate);
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = createBuybackTransfer(effectiveTransfer, settlementDate, externalId);
|
||||
externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer);
|
||||
return buildResponseData(externalAssetOwnerTransfer);
|
||||
}
|
||||
|
||||
private void validateEffectiveTransferForSale(final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
List<ExternalAssetOwnerTransfer> effectiveTransfers = externalAssetOwnerTransferRepository
|
||||
.findEffectiveTransfers(externalAssetOwnerTransfer.getLoanId(), externalAssetOwnerTransfer.getSettlementDate());
|
||||
|
||||
if (effectiveTransfers.size() > 0) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException("This loan cannot be sold, there is already an in progress transfer");
|
||||
}
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer fetchAndValidateEffectiveTransferForBuyback(final Long loanId, final LocalDate settlementDate) {
|
||||
List<ExternalAssetOwnerTransfer> effectiveTransfers = externalAssetOwnerTransferRepository.findEffectiveTransfers(loanId,
|
||||
settlementDate);
|
||||
|
||||
if (effectiveTransfers.size() == 0) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"This loan cannot be bought back, it is not owned by an external asset owner");
|
||||
} else if (effectiveTransfers.size() == 2) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"This loan cannot be bought back, external asset owner buyback transfer is already in progress");
|
||||
} else if (!BUYBACK_READY_STATUSES.contains(effectiveTransfers.get(0).getStatus())) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
String.format("This loan cannot be bought back, effective transfer is not in right state: %s",
|
||||
effectiveTransfers.get(0).getStatus()));
|
||||
} else if (settlementDate.isBefore(effectiveTransfers.get(0).getSettlementDate())) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
String.format("This loan cannot be bought back, settlement date is earlier than effective transfer settlement date: %s",
|
||||
effectiveTransfers.get(0).getSettlementDate()));
|
||||
}
|
||||
|
||||
return effectiveTransfers.get(0);
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer createBuybackTransfer(ExternalAssetOwnerTransfer effectiveTransfer, LocalDate settlementDate,
|
||||
ExternalId externalId) {
|
||||
LocalDate effectiveDateFrom = DateUtils.getBusinessLocalDate();
|
||||
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
externalAssetOwnerTransfer.setExternalId(externalId);
|
||||
externalAssetOwnerTransfer.setOwnerId(effectiveTransfer.getOwnerId());
|
||||
externalAssetOwnerTransfer.setStatus(ExternalTransferStatus.BUYBACK);
|
||||
externalAssetOwnerTransfer.setLoanId(effectiveTransfer.getLoanId());
|
||||
externalAssetOwnerTransfer.setExternalLoanId(effectiveTransfer.getExternalLoanId());
|
||||
externalAssetOwnerTransfer.setOwner(effectiveTransfer.getOwner());
|
||||
externalAssetOwnerTransfer.setSettlementDate(settlementDate);
|
||||
externalAssetOwnerTransfer.setEffectiveDateFrom(effectiveDateFrom);
|
||||
externalAssetOwnerTransfer.setEffectiveDateTo(FUTURE_DATE_9999_12_31);
|
||||
externalAssetOwnerTransfer.setPurchasePriceRatio(effectiveTransfer.getPurchasePriceRatio());
|
||||
return externalAssetOwnerTransfer;
|
||||
}
|
||||
|
||||
private CommandProcessingResult buildResponseData(ExternalAssetOwnerTransfer savedExternalAssetOwnerTransfer) {
|
||||
@@ -98,8 +161,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
changes.put(ExternalTransferRequestParameters.SETTLEMENT_DATE, savedExternalAssetOwnerTransfer.getSettlementDate());
|
||||
changes.put(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID,
|
||||
savedExternalAssetOwnerTransfer.getOwner().getExternalId().getValue());
|
||||
changes.put(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID,
|
||||
savedExternalAssetOwnerTransfer.getOwner().getExternalId().getValue());
|
||||
changes.put(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, savedExternalAssetOwnerTransfer.getExternalId().getValue());
|
||||
changes.put(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO, savedExternalAssetOwnerTransfer.getPurchasePriceRatio());
|
||||
return new CommandProcessingResultBuilder().withEntityId(savedExternalAssetOwnerTransfer.getId())
|
||||
.withEntityExternalId(savedExternalAssetOwnerTransfer.getExternalId())
|
||||
@@ -112,11 +174,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
private void validateSale(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
validateSettlementDate(externalAssetOwnerTransfer);
|
||||
validateTransferStatusForSale(externalAssetOwnerTransfer);
|
||||
}
|
||||
|
||||
private void validateBuyBack(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
validateSettlementDate(externalAssetOwnerTransfer);
|
||||
validateTransferStatusForBuyBack(externalAssetOwnerTransfer);
|
||||
validateEffectiveTransferForSale(externalAssetOwnerTransfer);
|
||||
}
|
||||
|
||||
private void validateSettlementDate(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
@@ -125,9 +183,14 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLoanStatus(LoanIdAndExternalIdData loanIdAndExternalIdAndExternalId) {
|
||||
if (Objects.isNull(loanIdAndExternalIdAndExternalId.getLoanId())
|
||||
&& Objects.isNull(loanIdAndExternalIdAndExternalId.getLoanExternalId())) {
|
||||
private void validateSettlementDate(LocalDate settlementDate) {
|
||||
if (settlementDate.isBefore(ThreadLocalContextUtil.getBusinessDate())) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException("Settlement date cannot be in the past");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLoanStatus(LoanIdAndExternalIdAndStatus entity) {
|
||||
if (!NON_CLOSED_LOAN_STATUSES.contains(LoanStatus.fromInt(entity.getLoanStatus()))) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException("Loan is not in active status");
|
||||
}
|
||||
}
|
||||
@@ -140,52 +203,35 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
ExternalTransferStatus latestTransferStatus = latestTransfer.getStatus();
|
||||
if (latestTransferStatus.equals(ExternalTransferStatus.PENDING)) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"External asset owner transfer is already in PENDING state for this loan.");
|
||||
"External asset owner transfer is already in PENDING state for this loan");
|
||||
} else if (latestTransferStatus.equals(ExternalTransferStatus.ACTIVE)) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"This loan cannot be sold, because it is owned by an external asset owner.");
|
||||
"This loan cannot be sold, because it is owned by an external asset owner");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateTransferStatusForBuyBack(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
Optional<ExternalAssetOwnerTransfer> latestTransferOptional = externalAssetOwnerTransferRepository
|
||||
.findLatestByLoanId(externalAssetOwnerTransfer.getLoanId());
|
||||
if (latestTransferOptional.isEmpty()) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"This loan cannot be bought back, because it is not owned by an external asset owner");
|
||||
} else {
|
||||
ExternalAssetOwnerTransfer latestTransfer = latestTransferOptional.get();
|
||||
ExternalTransferStatus latestTransferStatus = latestTransfer.getStatus();
|
||||
if (latestTransferStatus.equals(ExternalTransferStatus.BUYBACK)) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"External asset owner transfer is already in BUYBACK state for this loan.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer parseJson(Long loanId, String apiRequestBodyAsJson, ExternalId externalLoanId,
|
||||
ExternalTransferStatus status) {
|
||||
private ExternalAssetOwnerTransfer createSaleTransfer(Long loanId, String apiRequestBodyAsJson, ExternalId externalLoanId) {
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
|
||||
validateRequestBody(apiRequestBodyAsJson);
|
||||
LocalDate effectiveFrom = ThreadLocalContextUtil.getBusinessDate();
|
||||
validateSaleRequestBody(apiRequestBodyAsJson);
|
||||
final JsonElement json = fromApiJsonHelper.parse(apiRequestBodyAsJson);
|
||||
|
||||
ExternalAssetOwner owner = getOwner(json);
|
||||
externalAssetOwnerTransfer.setOwnerId(owner.getId());
|
||||
externalAssetOwnerTransfer.setOwner(owner);
|
||||
externalAssetOwnerTransfer.setExternalId(getTransferExternalIdFromJson(json));
|
||||
externalAssetOwnerTransfer.setStatus(status);
|
||||
externalAssetOwnerTransfer.setStatus(ExternalTransferStatus.PENDING);
|
||||
externalAssetOwnerTransfer.setPurchasePriceRatio(getPurchasePriceRatioFromJson(json));
|
||||
externalAssetOwnerTransfer.setSettlementDate(getSettlementDateFromJson(json));
|
||||
externalAssetOwnerTransfer.setEffectiveDateFrom(ThreadLocalContextUtil.getBusinessDate());
|
||||
externalAssetOwnerTransfer.setEffectiveDateTo(LocalDate.of(9999, 12, 31));
|
||||
externalAssetOwnerTransfer.setEffectiveDateFrom(effectiveFrom);
|
||||
externalAssetOwnerTransfer.setEffectiveDateTo(FUTURE_DATE_9999_12_31);
|
||||
externalAssetOwnerTransfer.setLoanId(loanId);
|
||||
externalAssetOwnerTransfer.setExternalLoanId(externalLoanId);
|
||||
return externalAssetOwnerTransfer;
|
||||
}
|
||||
|
||||
private void validateRequestBody(String apiRequestBodyAsJson) {
|
||||
private void validateSaleRequestBody(String apiRequestBodyAsJson) {
|
||||
final Set<String> requestParameters = new HashSet<>(
|
||||
Arrays.asList(ExternalTransferRequestParameters.SETTLEMENT_DATE, ExternalTransferRequestParameters.OWNER_EXTERNAL_ID,
|
||||
ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO,
|
||||
@@ -248,4 +294,10 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
externalAssetOwner.setExternalId(ExternalIdFactory.produce(externalId));
|
||||
return externalAssetOwnerRepository.saveAndFlush(externalAssetOwner);
|
||||
}
|
||||
|
||||
private LoanIdAndExternalIdAndStatus fetchLoanDetails(Long loanId) {
|
||||
Optional<LoanIdAndExternalIdAndStatus> loanIdAndExternalIdAndStatusResult = loanRepository
|
||||
.findLoanIdAndExternalIdAndStatusByLoanId(loanId);
|
||||
return loanIdAndExternalIdAndStatusResult.orElseThrow(() -> new LoanNotFoundException(loanId));
|
||||
}
|
||||
}
|
||||
|
||||
+90
-65
@@ -33,6 +33,7 @@ import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
@@ -80,7 +81,6 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
Long loanId = 1L;
|
||||
LocalDate settlementDate = actualDate;
|
||||
when(loanForProcessing.getId()).thenReturn(loanId);
|
||||
// when
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
@@ -90,33 +90,13 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanTooManyTransfer() {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
Long loanId = 1L;
|
||||
LocalDate settlementDate = actualDate;
|
||||
when(loanForProcessing.getId()).thenReturn(loanId);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer thirdResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem, secondResponseItem, thirdResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
// when
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> underTest.execute(loanForProcessing));
|
||||
// then
|
||||
assertEquals("Found too many owner transfers(3) by the settlement date(" + actualDate + ")", exception.getMessage());
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanTwoTransferButInvalidSecondTransfer() {
|
||||
public void givenLoanTwoTransferButInvalidTransfers() {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.PENDING);
|
||||
when(secondResponseItem.getStatus()).thenReturn(ExternalTransferStatus.ACTIVE);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem, secondResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
@@ -124,26 +104,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
// when
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> underTest.execute(loanForProcessing));
|
||||
// then
|
||||
assertEquals("Illegal transfer found. Expected BUYBACK, found: ACTIVE", exception.getMessage());
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanTwoTransferButInvalidFirstTransfer() {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.BUYBACK);
|
||||
when(secondResponseItem.getStatus()).thenReturn(ExternalTransferStatus.BUYBACK);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem, secondResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
// when
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> underTest.execute(loanForProcessing));
|
||||
// then
|
||||
assertEquals("Illegal transfer found. Expected PENDING or ACTIVE, found: BUYBACK", exception.getMessage());
|
||||
assertEquals("Illegal transfer found. Expected PENDING and BUYBACK, found: PENDING and ACTIVE", exception.getMessage());
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
}
|
||||
|
||||
@@ -208,11 +169,12 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.ACTIVE);
|
||||
when(secondResponseItem.getStatus()).thenReturn(ExternalTransferStatus.BUYBACK);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem, secondResponseItem);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.BUYBACK);
|
||||
when(firstResponseItem.getEffectiveDateFrom()).thenReturn(actualDate);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
when(externalAssetOwnerTransferRepository.findOne(any(Specification.class))).thenReturn(Optional.of(secondResponseItem));
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(firstResponseItem)).thenReturn(firstResponseItem);
|
||||
@@ -221,31 +183,14 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
// then
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
verify(firstResponseItem).setEffectiveDateTo(actualDate);
|
||||
verify(firstResponseItem).setEffectiveDateTo(firstResponseItem.getEffectiveDateFrom());
|
||||
verify(externalAssetOwnerTransferRepository, times(2)).save(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
verify(secondResponseItem).setEffectiveDateTo(secondResponseItem.getEffectiveDateFrom());
|
||||
verify(secondResponseItem).setEffectiveDateTo(actualDate);
|
||||
verify(externalAssetOwnerTransferLoanMappingRepository, times(1)).deleteByLoanIdAndOwnerTransfer(1L, secondResponseItem);
|
||||
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanOneTransferButInvalidTransfer() {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.ACTIVE);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
// when
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> underTest.execute(loanForProcessing));
|
||||
// then
|
||||
assertEquals("Illegal transfer found. Expected PENDING, found: ACTIVE", exception.getMessage());
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanSale() {
|
||||
// given
|
||||
@@ -291,6 +236,86 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanSaleButBalanceIsZero() {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.PENDING);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer newTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(firstResponseItem).thenReturn(newTransfer);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ZERO);
|
||||
when(loanForProcessing.getTotalOverpaid()).thenReturn(BigDecimal.ZERO);
|
||||
// when
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
// then
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
verify(firstResponseItem).setEffectiveDateTo(actualDate);
|
||||
verify(externalAssetOwnerTransferRepository, times(2)).save(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getOwner(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getOwner());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getExternalId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getExternalId());
|
||||
assertEquals(ExternalTransferStatus.DECLINED, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getStatus());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getSettlementDate());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getLoanId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getLoanId());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getPurchasePriceRatio(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getPurchasePriceRatio());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateFrom());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateTo());
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanSaleButBalanceIsNegative() {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.PENDING);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer newTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(firstResponseItem).thenReturn(newTransfer);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ONE.negate());
|
||||
when(loanForProcessing.getTotalOverpaid()).thenReturn(BigDecimal.ONE.negate());
|
||||
// when
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
// then
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
verify(firstResponseItem).setEffectiveDateTo(actualDate);
|
||||
verify(externalAssetOwnerTransferRepository, times(2)).save(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getOwner(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getOwner());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getExternalId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getExternalId());
|
||||
assertEquals(ExternalTransferStatus.DECLINED, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getStatus());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getSettlementDate());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getLoanId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getLoanId());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getPurchasePriceRatio(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getPurchasePriceRatio());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateFrom());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateTo());
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetEnumStyledNameSuccessScenario() {
|
||||
final String actualEnumName = underTest.getEnumStyledName();
|
||||
|
||||
Reference in New Issue
Block a user