blob: 99b9c8ef8a02b6c60d1b1ee62cf53d1ac0a0f7ad [file] [log] [blame]
/**
* 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 java.util.Objects;
import java.util.Optional;
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.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccountSnapshotBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition;
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.ExternalAssetOwnerTransferDetails;
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.investor.domain.LoanOwnershipTransferBusinessEvent;
import org.apache.fineract.investor.service.AccountingService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.springframework.context.annotation.Conditional;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
@Slf4j
@Conditional(InvestorModuleIsEnabledCondition.class)
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;
private final AccountingService accountingService;
private final BusinessEventNotifierService businessEventNotifierService;
@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.BUYBACK)),
criteriaBuilder.greaterThanOrEqualTo(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31)),
Sort.by(Sort.Direction.ASC, "id"));
int size = transferDataList.size();
if (size == 2) {
ExternalTransferStatus firstTransferStatus = transferDataList.get(0).getStatus();
ExternalTransferStatus secondTransferStatus = transferDataList.get(1).getStatus();
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, loan);
} else if (size == 1) {
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);
}
}
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);
businessEventNotifierService.notifyPostBusinessEvent(new LoanOwnershipTransferBusinessEvent(newExternalAssetOwnerTransfer, loan));
if (!ExternalTransferStatus.DECLINED.equals(newExternalAssetOwnerTransfer.getStatus())) {
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountSnapshotBusinessEvent(loan));
}
}
private void handleBuyback(final Loan loan, final LocalDate settlementDate,
final ExternalAssetOwnerTransfer buybackExternalAssetOwnerTransfer) {
Optional<ExternalAssetOwnerTransfer> optActiveExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository
.findOne((root, query, criteriaBuilder) -> criteriaBuilder.and(criteriaBuilder.equal(root.get("loanId"), loan.getId()),
criteriaBuilder.equal(root.get("owner"), buybackExternalAssetOwnerTransfer.getOwner()),
criteriaBuilder.equal(root.get("status"), ExternalTransferStatus.ACTIVE),
criteriaBuilder.equal(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31)));
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer;
if (!optActiveExternalAssetOwnerTransfer.isPresent()) {
newExternalAssetOwnerTransfer = createNewEntry(settlementDate, buybackExternalAssetOwnerTransfer,
ExternalTransferStatus.CANCELLED, ExternalTransferSubStatus.UNSOLD, settlementDate, settlementDate);
} else {
newExternalAssetOwnerTransfer = buybackAsset(loan, settlementDate, buybackExternalAssetOwnerTransfer,
optActiveExternalAssetOwnerTransfer.get());
}
businessEventNotifierService.notifyPostBusinessEvent(new LoanOwnershipTransferBusinessEvent(newExternalAssetOwnerTransfer, loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountSnapshotBusinessEvent(loan));
}
private ExternalAssetOwnerTransfer buybackAsset(final Loan loan, final LocalDate settlementDate,
ExternalAssetOwnerTransfer buybackExternalAssetOwnerTransfer, ExternalAssetOwnerTransfer activeExternalAssetOwnerTransfer) {
activeExternalAssetOwnerTransfer.setEffectiveDateTo(settlementDate);
buybackExternalAssetOwnerTransfer.setEffectiveDateTo(settlementDate);
buybackExternalAssetOwnerTransfer
.setExternalAssetOwnerTransferDetails(createAssetOwnerTransferDetails(loan, buybackExternalAssetOwnerTransfer));
externalAssetOwnerTransferRepository.save(activeExternalAssetOwnerTransfer);
buybackExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository.save(buybackExternalAssetOwnerTransfer);
externalAssetOwnerTransferLoanMappingRepository.deleteByLoanIdAndOwnerTransfer(loan.getId(), activeExternalAssetOwnerTransfer);
accountingService.createJournalEntriesForBuybackAssetTransfer(loan, buybackExternalAssetOwnerTransfer);
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);
newExternalAssetOwnerTransfer
.setExternalAssetOwnerTransferDetails(createAssetOwnerTransferDetails(loan, newExternalAssetOwnerTransfer));
accountingService.createJournalEntriesForSaleAssetTransfer(loan, newExternalAssetOwnerTransfer);
} else {
ExternalTransferSubStatus subStatus = ExternalTransferSubStatus.BALANCE_ZERO;
if (MathUtil.nullToDefault(loan.getTotalOverpaid(), BigDecimal.ZERO).compareTo(BigDecimal.ZERO) > 0) {
subStatus = ExternalTransferSubStatus.BALANCE_NEGATIVE;
}
newExternalAssetOwnerTransfer = createNewEntry(settlementDate, externalAssetOwnerTransfer, ExternalTransferStatus.DECLINED,
subStatus, settlementDate, settlementDate);
}
return newExternalAssetOwnerTransfer;
}
private ExternalAssetOwnerTransferDetails createAssetOwnerTransferDetails(Loan loan,
ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
ExternalAssetOwnerTransferDetails details = new ExternalAssetOwnerTransferDetails();
details.setExternalAssetOwnerTransfer(externalAssetOwnerTransfer);
details.setTotalOutstanding(Objects.requireNonNullElse(loan.getLoanSummary().getTotalOutstanding(), BigDecimal.ZERO));
details.setTotalPrincipalOutstanding(
Objects.requireNonNullElse(loan.getLoanSummary().getTotalPrincipalOutstanding(), BigDecimal.ZERO));
details.setTotalInterestOutstanding(
Objects.requireNonNullElse(loan.getLoanSummary().getTotalInterestOutstanding(), BigDecimal.ZERO));
details.setTotalFeeChargesOutstanding(
Objects.requireNonNullElse(loan.getLoanSummary().getTotalFeeChargesOutstanding(), BigDecimal.ZERO));
details.setTotalPenaltyChargesOutstanding(
Objects.requireNonNullElse(loan.getLoanSummary().getTotalPenaltyChargesOutstanding(), BigDecimal.ZERO));
details.setTotalOverpaid(Objects.requireNonNullElse(loan.getTotalOverpaid(), BigDecimal.ZERO));
return details;
}
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 MathUtil.nullToDefault(loan.getLoanSummary().getTotalOutstanding(), BigDecimal.ZERO).compareTo(BigDecimal.ZERO) > 0;
}
private void handleSameDaySaleAndBuyback(final LocalDate settlementDate, final List<ExternalAssetOwnerTransfer> transferDataList,
Loan loan) {
ExternalAssetOwnerTransfer cancelledPendingTransfer = cancelTransfer(settlementDate, transferDataList.get(0));
ExternalAssetOwnerTransfer cancelledBuybackTransfer = cancelTransfer(settlementDate, transferDataList.get(1));
businessEventNotifierService.notifyPostBusinessEvent(new LoanOwnershipTransferBusinessEvent(cancelledPendingTransfer, loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanOwnershipTransferBusinessEvent(cancelledBuybackTransfer, loan));
}
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";
}
}