| /** |
| * 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"; |
| } |
| } |