FINERACT-1926: Asset transfer to external owner COB business step
This commit is contained in:
+195
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.investor.cob.loan;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.cob.loan.LoanCOBBusinessStep;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.investor.data.ExternalTransferStatus;
|
||||
import org.apache.fineract.investor.data.ExternalTransferSubStatus;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMapping;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep {
|
||||
|
||||
public static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||
private final ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
private final ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
|
||||
|
||||
@Override
|
||||
public Loan execute(Loan loan) {
|
||||
Long loanId = loan.getId();
|
||||
log.debug("start processing loan ownership transfer business step for loan with Id [{}]", loanId);
|
||||
|
||||
LocalDate settlementDate = DateUtils.getBusinessLocalDate();
|
||||
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))),
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
} 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()));
|
||||
}
|
||||
handleSale(loan, settlementDate, externalAssetOwnerTransfer);
|
||||
}
|
||||
|
||||
log.debug("end processing loan ownership transfer business step for loan Id [{}]", loan.getId());
|
||||
return loan;
|
||||
}
|
||||
|
||||
private void handleSale(final Loan loan, final LocalDate settlementDate, final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = sellAsset(loan, settlementDate, externalAssetOwnerTransfer);
|
||||
// TODO: trigger asset loan transfer executed event
|
||||
}
|
||||
|
||||
private void handleBuyback(final Loan loan, final LocalDate settlementDate,
|
||||
final List<ExternalAssetOwnerTransfer> externalAssetOwnerTransferList) {
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = buybackAsset(loan, settlementDate, externalAssetOwnerTransferList);
|
||||
// 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);
|
||||
buybackExternalAssetOwnerTransfer.setEffectiveDateTo(buybackExternalAssetOwnerTransfer.getEffectiveDateFrom());
|
||||
externalAssetOwnerTransferRepository.save(saleExternalAssetOwnerTransfer);
|
||||
buybackExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository.save(buybackExternalAssetOwnerTransfer);
|
||||
externalAssetOwnerTransferLoanMappingRepository.deleteByLoanIdAndOwnerTransfer(loan.getId(), buybackExternalAssetOwnerTransfer);
|
||||
// TODO: create asset ownership accounting entries
|
||||
// TODO: create asset ownership transaction entries
|
||||
return buybackExternalAssetOwnerTransfer;
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer sellAsset(final Loan loan, final LocalDate settlementDate,
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer;
|
||||
if (isTransferable(loan)) {
|
||||
newExternalAssetOwnerTransfer = createActiveEntry(settlementDate, externalAssetOwnerTransfer);
|
||||
createActiveMapping(loan.getId(), newExternalAssetOwnerTransfer);
|
||||
// TODO: create asset ownership accounting entries
|
||||
// TODO: create asset ownership transaction entries
|
||||
} else {
|
||||
ExternalTransferSubStatus subStatus = ExternalTransferSubStatus.BALANCE_ZERO;
|
||||
if (loan.getTotalOverpaid().compareTo(BigDecimal.ZERO) > 0) {
|
||||
subStatus = ExternalTransferSubStatus.BALANCE_NEGATIVE;
|
||||
}
|
||||
newExternalAssetOwnerTransfer = createNewEntry(settlementDate, externalAssetOwnerTransfer, ExternalTransferStatus.DECLINED,
|
||||
subStatus, settlementDate, settlementDate);
|
||||
}
|
||||
return newExternalAssetOwnerTransfer;
|
||||
}
|
||||
|
||||
private void createActiveMapping(Long loanId, ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransferLoanMapping externalAssetOwnerTransferLoanMapping = new ExternalAssetOwnerTransferLoanMapping();
|
||||
externalAssetOwnerTransferLoanMapping.setLoanId(loanId);
|
||||
externalAssetOwnerTransferLoanMapping.setOwnerTransfer(externalAssetOwnerTransfer);
|
||||
externalAssetOwnerTransferLoanMappingRepository.save(externalAssetOwnerTransferLoanMapping);
|
||||
}
|
||||
|
||||
private boolean isTransferable(final Loan loan) {
|
||||
return loan.getLoanSummary().getTotalOutstanding().compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
|
||||
private void handleSameDaySaleAndBuyback(final LocalDate settlementDate, final List<ExternalAssetOwnerTransfer> transferDataList) {
|
||||
ExternalAssetOwnerTransfer cancelledPendingTransfer = cancelTransfer(settlementDate, transferDataList.get(0));
|
||||
ExternalAssetOwnerTransfer cancelledBuybackTransfer = cancelTransfer(settlementDate, transferDataList.get(1));
|
||||
// TODO: trigger asset loan transfer cancel events
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer cancelTransfer(final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
return createNewEntry(settlementDate, externalAssetOwnerTransfer, ExternalTransferStatus.CANCELLED,
|
||||
ExternalTransferSubStatus.SAMEDAY_TRANSFERS, settlementDate, settlementDate);
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer createNewEntry(final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer externalAssetOwnerTransfer, final ExternalTransferStatus status,
|
||||
final ExternalTransferSubStatus subStatus, final LocalDate effectiveDateFrom, final LocalDate effectiveDateTo) {
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
newExternalAssetOwnerTransfer.setOwner(externalAssetOwnerTransfer.getOwner());
|
||||
newExternalAssetOwnerTransfer.setExternalId(externalAssetOwnerTransfer.getExternalId());
|
||||
newExternalAssetOwnerTransfer.setStatus(status);
|
||||
newExternalAssetOwnerTransfer.setSubStatus(subStatus);
|
||||
newExternalAssetOwnerTransfer.setSettlementDate(settlementDate);
|
||||
newExternalAssetOwnerTransfer.setLoanId(externalAssetOwnerTransfer.getLoanId());
|
||||
newExternalAssetOwnerTransfer.setExternalLoanId(externalAssetOwnerTransfer.getExternalLoanId());
|
||||
newExternalAssetOwnerTransfer.setPurchasePriceRatio(externalAssetOwnerTransfer.getPurchasePriceRatio());
|
||||
newExternalAssetOwnerTransfer.setEffectiveDateFrom(effectiveDateFrom);
|
||||
newExternalAssetOwnerTransfer.setEffectiveDateTo(effectiveDateTo);
|
||||
|
||||
externalAssetOwnerTransfer.setEffectiveDateTo(settlementDate);
|
||||
externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer);
|
||||
return externalAssetOwnerTransferRepository.save(newExternalAssetOwnerTransfer);
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer createActiveEntry(final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
LocalDate effectiveFrom = settlementDate.plusDays(1);
|
||||
return createNewEntry(settlementDate, externalAssetOwnerTransfer, ExternalTransferStatus.ACTIVE, null, effectiveFrom,
|
||||
FUTURE_DATE_9999_12_31);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEnumStyledName() {
|
||||
return "EXTERNAL_ASSET_OWNER_TRANSFER";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableName() {
|
||||
return "Execute external asset owner transfer";
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.investor.data;
|
||||
|
||||
public enum ExternalTransferSubStatus {
|
||||
BALANCE_ZERO, BALANCE_NEGATIVE, SAMEDAY_TRANSFERS, USER_REQUESTED
|
||||
}
|
||||
+18
-9
@@ -21,6 +21,8 @@ package org.apache.fineract.investor.domain;
|
||||
import java.time.LocalDate;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
@@ -29,6 +31,8 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.investor.data.ExternalTransferStatus;
|
||||
import org.apache.fineract.investor.data.ExternalTransferSubStatus;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -37,29 +41,34 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
@Entity
|
||||
public class ExternalAssetOwnerTransfer extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
|
||||
@Column(name = "owner_id")
|
||||
@Column(name = "owner_id", nullable = false)
|
||||
private Long ownerId;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "owner_id", insertable = false, updatable = false)
|
||||
@JoinColumn(name = "owner_id", insertable = false, updatable = false, nullable = false)
|
||||
private ExternalAssetOwner owner;
|
||||
|
||||
@Column(name = "external_id", length = 100)
|
||||
@Column(name = "external_id", length = 100, nullable = false)
|
||||
private ExternalId externalId;
|
||||
|
||||
@Column(name = "status", length = 50)
|
||||
private String status;
|
||||
@Column(name = "status", length = 50, nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private ExternalTransferStatus status;
|
||||
|
||||
@Column(name = "purchase_price_ratio", length = 50)
|
||||
@Column(name = "sub_status", length = 50)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private ExternalTransferSubStatus subStatus;
|
||||
|
||||
@Column(name = "purchase_price_ratio", length = 50, nullable = false)
|
||||
private String purchasePriceRatio;
|
||||
|
||||
@Column(name = "settlement_date")
|
||||
@Column(name = "settlement_date", nullable = false)
|
||||
private LocalDate settlementDate;
|
||||
|
||||
@Column(name = "effective_date_from")
|
||||
@Column(name = "effective_date_from", nullable = false)
|
||||
private LocalDate effectiveDateFrom;
|
||||
|
||||
@Column(name = "effective_date_to")
|
||||
@Column(name = "effective_date_to", nullable = false)
|
||||
private LocalDate effectiveDateTo;
|
||||
|
||||
@Column(name = "loan_id", nullable = false)
|
||||
|
||||
+2
-3
@@ -23,7 +23,6 @@ import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
@@ -32,7 +31,7 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDa
|
||||
@Getter
|
||||
@Setter
|
||||
@Table(name = "m_external_asset_owner_transfer_loan_mapping")
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@NoArgsConstructor
|
||||
@Entity
|
||||
public class ExternalAssetOwnerTransferLoanMapping extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
|
||||
@@ -40,7 +39,7 @@ public class ExternalAssetOwnerTransferLoanMapping extends AbstractAuditableWith
|
||||
private Long loanId;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "owner_transfer_id")
|
||||
@JoinColumn(name = "owner_transfer_id", nullable = false)
|
||||
private ExternalAssetOwnerTransfer ownerTransfer;
|
||||
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.investor.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface ExternalAssetOwnerTransferLoanMappingRepository extends JpaRepository<ExternalAssetOwnerTransferLoanMapping, Long>,
|
||||
JpaSpecificationExecutor<ExternalAssetOwnerTransferLoanMapping> {
|
||||
|
||||
void deleteByLoanIdAndOwnerTransfer(Long loanId, ExternalAssetOwnerTransfer externalAssetOwnerTransfer);
|
||||
}
|
||||
+1
@@ -37,4 +37,5 @@ 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);
|
||||
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.investor.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
import org.apache.fineract.investor.data.ExternalTransferStatus;
|
||||
|
||||
public class ExternalAssetOwnerTransferNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||
|
||||
public ExternalAssetOwnerTransferNotFoundException(ExternalId externalId, ExternalTransferStatus externalTransferStatus) {
|
||||
super("error.msg.external.asset.owner.transfer.external.id.and.status",
|
||||
String.format("External asset owner transfer with external id: %s and status: %s does not found", externalId.getValue(),
|
||||
externalTransferStatus),
|
||||
externalId.getValue(), externalTransferStatus);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -34,6 +34,6 @@ public class BuybackLoanFromExternalAssetOwnerHandler implements NewCommandSourc
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(JsonCommand command) {
|
||||
return externalAssetOwnersWriteService.buyBackLoanByLoanId(command);
|
||||
return externalAssetOwnersWriteService.buybackLoanByLoanId(command);
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -24,4 +24,5 @@ import org.apache.fineract.investor.data.ExternalTransferData;
|
||||
public interface ExternalAssetOwnersReadService {
|
||||
|
||||
List<ExternalTransferData> retrieveTransferData(Long loanId, String externalLoanId, String transferExternalId);
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -25,5 +25,6 @@ public interface ExternalAssetOwnersWriteService {
|
||||
|
||||
CommandProcessingResult saleLoanByLoanId(JsonCommand command);
|
||||
|
||||
CommandProcessingResult buyBackLoanByLoanId(JsonCommand command);
|
||||
CommandProcessingResult buybackLoanByLoanId(JsonCommand command);
|
||||
|
||||
}
|
||||
|
||||
+9
-5
@@ -50,6 +50,7 @@ import org.apache.fineract.investor.data.ExternalTransferStatus;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwner;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerRepository;
|
||||
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;
|
||||
@@ -61,6 +62,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersWriteService {
|
||||
|
||||
private final ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
private final ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
|
||||
private final ExternalAssetOwnerRepository externalAssetOwnerRepository;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanReadPlatformServiceCommon loanReadPlatformService;
|
||||
@@ -80,7 +82,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult buyBackLoanByLoanId(JsonCommand command) {
|
||||
public CommandProcessingResult buybackLoanByLoanId(JsonCommand command) {
|
||||
Long loanId = command.getLoanId();
|
||||
LoanIdAndExternalIdData loanIdAndExternalId = loanReadPlatformService.getTransferableLoanIdAndExternalId(loanId);
|
||||
validateLoanStatus(loanIdAndExternalId);
|
||||
@@ -135,7 +137,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
.findLatestByLoanId(externalAssetOwnerTransfer.getLoanId());
|
||||
if (latestTransferOptional.isPresent()) {
|
||||
ExternalAssetOwnerTransfer latestTransfer = latestTransferOptional.get();
|
||||
ExternalTransferStatus latestTransferStatus = ExternalTransferStatus.valueOf(latestTransfer.getStatus());
|
||||
ExternalTransferStatus latestTransferStatus = latestTransfer.getStatus();
|
||||
if (latestTransferStatus.equals(ExternalTransferStatus.PENDING)) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"External asset owner transfer is already in PENDING state for this loan.");
|
||||
@@ -154,7 +156,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
"This loan cannot be bought back, because it is not owned by an external asset owner");
|
||||
} else {
|
||||
ExternalAssetOwnerTransfer latestTransfer = latestTransferOptional.get();
|
||||
ExternalTransferStatus latestTransferStatus = ExternalTransferStatus.valueOf(latestTransfer.getStatus());
|
||||
ExternalTransferStatus latestTransferStatus = latestTransfer.getStatus();
|
||||
if (latestTransferStatus.equals(ExternalTransferStatus.BUYBACK)) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"External asset owner transfer is already in BUYBACK state for this loan.");
|
||||
@@ -173,7 +175,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
externalAssetOwnerTransfer.setOwnerId(owner.getId());
|
||||
externalAssetOwnerTransfer.setOwner(owner);
|
||||
externalAssetOwnerTransfer.setExternalId(getTransferExternalIdFromJson(json));
|
||||
externalAssetOwnerTransfer.setStatus(status.name());
|
||||
externalAssetOwnerTransfer.setStatus(status);
|
||||
externalAssetOwnerTransfer.setPurchasePriceRatio(getPurchasePriceRatioFromJson(json));
|
||||
externalAssetOwnerTransfer.setSettlementDate(getSettlementDateFromJson(json));
|
||||
externalAssetOwnerTransfer.setEffectiveDateFrom(ThreadLocalContextUtil.getBusinessDate());
|
||||
@@ -188,7 +190,9 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
Arrays.asList(ExternalTransferRequestParameters.SETTLEMENT_DATE, ExternalTransferRequestParameters.OWNER_EXTERNAL_ID,
|
||||
ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO,
|
||||
ExternalTransferRequestParameters.DATEFORMAT, ExternalTransferRequestParameters.LOCALE));
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, apiRequestBodyAsJson, requestParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
|
||||
+1
@@ -27,4 +27,5 @@
|
||||
<include relativeToChangelogFile="true" file="parts/0003_asset_schemas.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/0004_change_purchase_price_ratio_type.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/0005_add_sale_and_buyback_command.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/0006_asset_schemas.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<changeSet author="fineract" id="1">
|
||||
<addColumn tableName="m_external_asset_owner_transfer">
|
||||
<column name="sub_status" type="VARCHAR(50)"/>
|
||||
</addColumn>
|
||||
|
||||
<createIndex tableName="m_external_asset_owner_transfer"
|
||||
indexName="external_asset_owner_transfer_sub_status">
|
||||
<column name="sub_status"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
<changeSet id="2" author="fineract">
|
||||
<addNotNullConstraint tableName="m_external_asset_owner_transfer" columnName="owner_id" columnDataType="BIGINT"/>
|
||||
<addNotNullConstraint tableName="m_external_asset_owner_transfer" columnName="external_id" columnDataType="VARCHAR(100)"/>
|
||||
<addNotNullConstraint tableName="m_external_asset_owner_transfer" columnName="status" columnDataType="VARCHAR(50)"/>
|
||||
<addNotNullConstraint tableName="m_external_asset_owner_transfer" columnName="purchase_price_ratio" columnDataType="VARCHAR(50)"/>
|
||||
<addNotNullConstraint tableName="m_external_asset_owner_transfer" columnName="settlement_date" columnDataType="DATE"/>
|
||||
<addNotNullConstraint tableName="m_external_asset_owner_transfer" columnName="effective_date_from" columnDataType="DATE"/>
|
||||
<addNotNullConstraint tableName="m_external_asset_owner_transfer" columnName="effective_date_to" columnDataType="DATE"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="3" author="fineract">
|
||||
<addNotNullConstraint tableName="m_external_asset_owner_transfer_loan_mapping" columnName="owner_transfer_id" columnDataType="BIGINT"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.investor.cob.loan;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.investor.data.ExternalTransferStatus;
|
||||
import org.apache.fineract.investor.data.ExternalTransferSubStatus;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMapping;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
|
||||
public static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||
private final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault());
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
|
||||
private LoanAccountOwnerTransferBusinessStep underTest;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
|
||||
underTest = new LoanAccountOwnerTransferBusinessStep(externalAssetOwnerTransferRepository,
|
||||
externalAssetOwnerTransferLoanMappingRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanNoTransfer() {
|
||||
// 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);
|
||||
// then
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
}
|
||||
|
||||
@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() {
|
||||
// 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(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"))))
|
||||
.thenReturn(response);
|
||||
// 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());
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanTwoTransferSameDay() {
|
||||
// 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.BUYBACK);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem, secondResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
// 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(4)).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.CANCELLED, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getStatus());
|
||||
assertEquals(ExternalTransferSubStatus.SAMEDAY_TRANSFERS,
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getSubStatus());
|
||||
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(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(2).getOwner(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(3).getOwner());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(2).getExternalId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(3).getExternalId());
|
||||
assertEquals(ExternalTransferStatus.CANCELLED, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(3).getStatus());
|
||||
assertEquals(ExternalTransferSubStatus.SAMEDAY_TRANSFERS,
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(3).getSubStatus());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(3).getSettlementDate());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(2).getLoanId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(3).getLoanId());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(2).getPurchasePriceRatio(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(3).getPurchasePriceRatio());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(3).getEffectiveDateFrom());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(3).getEffectiveDateTo());
|
||||
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanBuyback() {
|
||||
// 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.ACTIVE);
|
||||
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);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(firstResponseItem)).thenReturn(firstResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.save(secondResponseItem)).thenReturn(secondResponseItem);
|
||||
// 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());
|
||||
verify(secondResponseItem).setEffectiveDateTo(secondResponseItem.getEffectiveDateFrom());
|
||||
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
|
||||
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);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferLoanMapping> externalAssetOwnerTransferLoanMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferLoanMapping.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);
|
||||
// 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.ACTIVE, 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.plusDays(1), externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateFrom());
|
||||
assertEquals(FUTURE_DATE_9999_12_31, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateTo());
|
||||
verify(externalAssetOwnerTransferLoanMappingRepository, times(1))
|
||||
.save(externalAssetOwnerTransferLoanMappingArgumentCaptor.capture());
|
||||
assertEquals(1L, externalAssetOwnerTransferLoanMappingArgumentCaptor.getValue().getLoanId());
|
||||
assertEquals(newTransfer, externalAssetOwnerTransferLoanMappingArgumentCaptor.getValue().getOwnerTransfer());
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetEnumStyledNameSuccessScenario() {
|
||||
final String actualEnumName = underTest.getEnumStyledName();
|
||||
assertNotNull(actualEnumName);
|
||||
assertEquals("EXTERNAL_ASSET_OWNER_TRANSFER", actualEnumName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHumanReadableNameSuccessScenario() {
|
||||
final String actualEnumName = underTest.getHumanReadableName();
|
||||
assertNotNull(actualEnumName);
|
||||
assertEquals("Execute external asset owner transfer", actualEnumName);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user