FINERACT-2141: Delayed settlement implementation for fineract loan investor module
This commit is contained in:
committed by
Arnold Gálovics
parent
23887bdf1c
commit
33e9f7f3db
+3
-1
@@ -220,7 +220,9 @@ public class ExternalAssetOwnersApiResource {
|
||||
private String getResult(Long loanId, String apiRequestBodyAsJson, String commandParam) {
|
||||
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
||||
CommandWrapper commandRequest = null;
|
||||
if (CommandParameterUtil.is(commandParam, "sale")) {
|
||||
if (CommandParameterUtil.is(commandParam, "intermediarySale")) {
|
||||
commandRequest = builder.intermediarySaleLoanToExternalAssetOwner(loanId).build();
|
||||
} else if (CommandParameterUtil.is(commandParam, "sale")) {
|
||||
commandRequest = builder.saleLoanToExternalAssetOwner(loanId).build();
|
||||
} else if (CommandParameterUtil.is(commandParam, "buyback")) {
|
||||
commandRequest = builder.buybackLoanToExternalAssetOwner(loanId).build();
|
||||
|
||||
+6
@@ -61,6 +61,9 @@ final class ExternalAssetOwnersApiResourceSwagger {
|
||||
@Schema(example = "e1156fbe-38bb-42f8-b491-fca02075f40e")
|
||||
public String transferExternalId;
|
||||
|
||||
@Schema(example = "e1156fbe-38bb-42f8-b491-fca02075f40e")
|
||||
public String transferExternalGroupId;
|
||||
|
||||
@Schema(example = "1")
|
||||
public String purchasePriceRatio;
|
||||
|
||||
@@ -101,6 +104,9 @@ final class ExternalAssetOwnersApiResourceSwagger {
|
||||
@Schema(example = "36efeb06-d835-48a1-99eb-09bd1d348c1e")
|
||||
public String transferExternalId;
|
||||
|
||||
@Schema(example = "e1156fbe-38bb-42f8-b491-fca02075f40e")
|
||||
public String transferExternalGroupId;
|
||||
|
||||
@Schema(example = "1.2345678")
|
||||
public String purchasePriceRatio;
|
||||
|
||||
|
||||
+112
-36
@@ -23,16 +23,17 @@ import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
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.ExternalAssetOwner;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferDetails;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMapping;
|
||||
@@ -40,6 +41,8 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMapping
|
||||
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.investor.service.DelayedSettlementAttributeService;
|
||||
import org.apache.fineract.investor.service.LoanTransferabilityService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -52,10 +55,16 @@ import org.springframework.stereotype.Component;
|
||||
public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep {
|
||||
|
||||
public static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||
public static final List<ExternalTransferStatus> PENDING_STATUSES = List.of(ExternalTransferStatus.PENDING_INTERMEDIATE,
|
||||
ExternalTransferStatus.PENDING);
|
||||
public static final List<ExternalTransferStatus> BUYBACK_STATUSES = List.of(ExternalTransferStatus.BUYBACK_INTERMEDIATE,
|
||||
ExternalTransferStatus.BUYBACK);
|
||||
private final ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
private final ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
|
||||
private final AccountingService accountingService;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanTransferabilityService loanTransferabilityService;
|
||||
private final DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||
|
||||
@Override
|
||||
public Loan execute(Loan loan) {
|
||||
@@ -66,7 +75,7 @@ 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.BUYBACK)),
|
||||
root.get("status").in(Stream.concat(PENDING_STATUSES.stream(), BUYBACK_STATUSES.stream()).toList()),
|
||||
criteriaBuilder.greaterThanOrEqualTo(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31)),
|
||||
Sort.by(Sort.Direction.ASC, "id"));
|
||||
int size = transferDataList.size();
|
||||
@@ -75,6 +84,11 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
ExternalTransferStatus firstTransferStatus = transferDataList.get(0).getStatus();
|
||||
ExternalTransferStatus secondTransferStatus = transferDataList.get(1).getStatus();
|
||||
|
||||
if (delayedSettlementAttributeService.isEnabled(loan.getLoanProduct().getId())) {
|
||||
throw new IllegalStateException(String.format("Delayed Settlement enabled, but found 2 transfers of statuses: %s and %s",
|
||||
firstTransferStatus, secondTransferStatus));
|
||||
}
|
||||
|
||||
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",
|
||||
@@ -83,9 +97,9 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
handleSameDaySaleAndBuyback(settlementDate, transferDataList, loan);
|
||||
} else if (size == 1) {
|
||||
ExternalAssetOwnerTransfer transfer = transferDataList.get(0);
|
||||
if (ExternalTransferStatus.PENDING.equals(transfer.getStatus())) {
|
||||
if (PENDING_STATUSES.contains(transfer.getStatus())) {
|
||||
handleSale(loan, settlementDate, transfer);
|
||||
} else if (ExternalTransferStatus.BUYBACK.equals(transfer.getStatus())) {
|
||||
} else if (BUYBACK_STATUSES.contains(transfer.getStatus())) {
|
||||
handleBuyback(loan, settlementDate, transfer);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +109,8 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
}
|
||||
|
||||
private void handleSale(final Loan loan, final LocalDate settlementDate, final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = sellAsset(loan, settlementDate, externalAssetOwnerTransfer);
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = sellAssetOrDecline(loan, settlementDate, externalAssetOwnerTransfer);
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanOwnershipTransferBusinessEvent(newExternalAssetOwnerTransfer, loan));
|
||||
if (!ExternalTransferStatus.DECLINED.equals(newExternalAssetOwnerTransfer.getStatus())) {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountSnapshotBusinessEvent(loan));
|
||||
@@ -104,14 +119,16 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
|
||||
private void handleBuyback(final Loan loan, final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer buybackExternalAssetOwnerTransfer) {
|
||||
final ExternalTransferStatus expectedActiveStatus = determineExpectedActiveStatus(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("status"), expectedActiveStatus),
|
||||
criteriaBuilder.equal(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31)));
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer;
|
||||
if (!optActiveExternalAssetOwnerTransfer.isPresent()) {
|
||||
newExternalAssetOwnerTransfer = createNewEntry(settlementDate, buybackExternalAssetOwnerTransfer,
|
||||
newExternalAssetOwnerTransfer = createNewEntryAndExpireOldEntry(settlementDate, buybackExternalAssetOwnerTransfer,
|
||||
ExternalTransferStatus.CANCELLED, ExternalTransferSubStatus.UNSOLD, settlementDate, settlementDate);
|
||||
} else {
|
||||
newExternalAssetOwnerTransfer = buybackAsset(loan, settlementDate, buybackExternalAssetOwnerTransfer,
|
||||
@@ -134,26 +151,71 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
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);
|
||||
private ExternalAssetOwnerTransfer sellAssetOrDecline(final Loan loan, final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
if (!loanTransferabilityService.isTransferable(loan, externalAssetOwnerTransfer)) {
|
||||
// Validation fails. Decline asset sell.
|
||||
ExternalTransferSubStatus declinedSubStatus = loanTransferabilityService.getDeclinedSubStatus(loan);
|
||||
return declinePendingEntry(loan, settlementDate, externalAssetOwnerTransfer, declinedSubStatus);
|
||||
}
|
||||
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = sellAsset(loan, settlementDate, externalAssetOwnerTransfer);
|
||||
createActiveMapping(loan.getId(), newExternalAssetOwnerTransfer);
|
||||
newExternalAssetOwnerTransfer
|
||||
.setExternalAssetOwnerTransferDetails(createAssetOwnerTransferDetails(loan, newExternalAssetOwnerTransfer));
|
||||
|
||||
return newExternalAssetOwnerTransfer;
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer sellAsset(final Loan loan, final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwner previousOwner = determinePreviousOwnerAndCleanupIfNeeded(loan, settlementDate, externalAssetOwnerTransfer);
|
||||
ExternalTransferStatus activeStatus = determineActiveStatus(externalAssetOwnerTransfer);
|
||||
|
||||
ExternalAssetOwnerTransfer newTransfer = activatePendingEntry(settlementDate, externalAssetOwnerTransfer, activeStatus);
|
||||
accountingService.createJournalEntriesForSaleAssetTransfer(loan, newTransfer, previousOwner);
|
||||
return newTransfer;
|
||||
}
|
||||
|
||||
private ExternalAssetOwner determinePreviousOwnerAndCleanupIfNeeded(final Loan loan, final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
if (!delayedSettlementAttributeService.isEnabled(loan.getLoanProduct().getId())) {
|
||||
// When delayed settlement is disabled, asset is directly sold to investor, and we are the previous owner.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ExternalTransferStatus.PENDING_INTERMEDIATE == externalAssetOwnerTransfer.getStatus()) {
|
||||
// When delayed settlement is enabled and asset is sold to intermediate, we are the previous owner.
|
||||
return null;
|
||||
}
|
||||
|
||||
// When delayed settlement is enabled and asset is sold from intermediate to investor, the intermediate is the
|
||||
// previous owner.
|
||||
ExternalAssetOwnerTransfer activeIntermediateTransfer = getActiveIntermediateOrThrow(loan);
|
||||
expireTransfer(settlementDate, activeIntermediateTransfer);
|
||||
externalAssetOwnerTransferLoanMappingRepository.deleteByLoanIdAndOwnerTransfer(loan.getId(), activeIntermediateTransfer);
|
||||
|
||||
return activeIntermediateTransfer.getOwner();
|
||||
}
|
||||
|
||||
private ExternalTransferStatus determineActiveStatus(final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
if (ExternalTransferStatus.PENDING_INTERMEDIATE == externalAssetOwnerTransfer.getStatus()) {
|
||||
return ExternalTransferStatus.ACTIVE_INTERMEDIATE;
|
||||
}
|
||||
|
||||
return ExternalTransferStatus.ACTIVE;
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer getActiveIntermediateOrThrow(final Loan loan) {
|
||||
Optional<ExternalAssetOwnerTransfer> optionalActiveIntermediateTransfer = externalAssetOwnerTransferRepository
|
||||
.findOne((root, query, criteriaBuilder) -> criteriaBuilder.and(criteriaBuilder.equal(root.get("loanId"), loan.getId()),
|
||||
criteriaBuilder.equal(root.get("status"), ExternalTransferStatus.ACTIVE_INTERMEDIATE),
|
||||
criteriaBuilder.equal(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31)));
|
||||
|
||||
return optionalActiveIntermediateTransfer
|
||||
.orElseThrow(() -> new IllegalStateException("Expected a effective transfer of ACTIVE_INTERMEDIATE status to be present."));
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransferDetails createAssetOwnerTransferDetails(Loan loan,
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransferDetails details = new ExternalAssetOwnerTransferDetails();
|
||||
@@ -176,10 +238,6 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
externalAssetOwnerTransferLoanMappingRepository.save(externalAssetOwnerTransferLoanMapping);
|
||||
}
|
||||
|
||||
private boolean isTransferable(final Loan loan) {
|
||||
return MathUtil.nullToDefault(loan.getSummary().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));
|
||||
@@ -190,11 +248,23 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
|
||||
private ExternalAssetOwnerTransfer cancelTransfer(final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
return createNewEntry(settlementDate, externalAssetOwnerTransfer, ExternalTransferStatus.CANCELLED,
|
||||
return createNewEntryAndExpireOldEntry(settlementDate, externalAssetOwnerTransfer, ExternalTransferStatus.CANCELLED,
|
||||
ExternalTransferSubStatus.SAMEDAY_TRANSFERS, settlementDate, settlementDate);
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer createNewEntry(final LocalDate settlementDate,
|
||||
private ExternalAssetOwnerTransfer activatePendingEntry(final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer pendingTransfer, final ExternalTransferStatus activeStatus) {
|
||||
LocalDate effectiveFrom = settlementDate.plusDays(1);
|
||||
return createNewEntryAndExpireOldEntry(settlementDate, pendingTransfer, activeStatus, null, effectiveFrom, FUTURE_DATE_9999_12_31);
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer declinePendingEntry(final Loan loan, final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer pendingTransfer, ExternalTransferSubStatus subStatus) {
|
||||
return createNewEntryAndExpireOldEntry(settlementDate, pendingTransfer, ExternalTransferStatus.DECLINED, subStatus, settlementDate,
|
||||
settlementDate);
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer createNewEntryAndExpireOldEntry(final LocalDate settlementDate,
|
||||
final ExternalAssetOwnerTransfer externalAssetOwnerTransfer, final ExternalTransferStatus status,
|
||||
final ExternalTransferSubStatus subStatus, final LocalDate effectiveDateFrom, final LocalDate effectiveDateTo) {
|
||||
ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
@@ -205,20 +275,26 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
newExternalAssetOwnerTransfer.setSettlementDate(settlementDate);
|
||||
newExternalAssetOwnerTransfer.setLoanId(externalAssetOwnerTransfer.getLoanId());
|
||||
newExternalAssetOwnerTransfer.setExternalLoanId(externalAssetOwnerTransfer.getExternalLoanId());
|
||||
newExternalAssetOwnerTransfer.setExternalGroupId(externalAssetOwnerTransfer.getExternalGroupId());
|
||||
newExternalAssetOwnerTransfer.setPurchasePriceRatio(externalAssetOwnerTransfer.getPurchasePriceRatio());
|
||||
newExternalAssetOwnerTransfer.setEffectiveDateFrom(effectiveDateFrom);
|
||||
newExternalAssetOwnerTransfer.setEffectiveDateTo(effectiveDateTo);
|
||||
|
||||
externalAssetOwnerTransfer.setEffectiveDateTo(settlementDate);
|
||||
externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer);
|
||||
expireTransfer(settlementDate, 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);
|
||||
private void expireTransfer(final LocalDate settlementDate, final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
externalAssetOwnerTransfer.setEffectiveDateTo(settlementDate);
|
||||
externalAssetOwnerTransferRepository.save(externalAssetOwnerTransfer);
|
||||
}
|
||||
|
||||
private ExternalTransferStatus determineExpectedActiveStatus(final ExternalAssetOwnerTransfer buybackExternalAssetOwnerTransfer) {
|
||||
if (ExternalTransferStatus.BUYBACK_INTERMEDIATE == buybackExternalAssetOwnerTransfer.getStatus()) {
|
||||
return ExternalTransferStatus.ACTIVE_INTERMEDIATE;
|
||||
}
|
||||
return ExternalTransferStatus.ACTIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.config;
|
||||
|
||||
import org.apache.fineract.investor.service.DelayedSettlementAttributeService;
|
||||
import org.apache.fineract.investor.service.LoanTransferabilityService;
|
||||
import org.apache.fineract.investor.service.LoanTransferabilityServiceImpl;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class LoanAccountOwnerTransferConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanTransferabilityService.class)
|
||||
public LoanTransferabilityService loanTransferabilityService(DelayedSettlementAttributeService delayedSettlementAttributeService) {
|
||||
return new LoanTransferabilityServiceImpl(delayedSettlementAttributeService);
|
||||
}
|
||||
}
|
||||
+1
@@ -29,6 +29,7 @@ public class ExternalTransferData {
|
||||
private ExternalTransferLoanData loan;
|
||||
private ExternalTransferDataDetails details;
|
||||
private String transferExternalId;
|
||||
private String transferExternalGroupId;
|
||||
private String purchasePriceRatio;
|
||||
private LocalDate settlementDate;
|
||||
private ExternalTransferStatus status;
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ public final class ExternalTransferRequestParameters {
|
||||
public static final String SETTLEMENT_DATE = "settlementDate";
|
||||
public static final String OWNER_EXTERNAL_ID = "ownerExternalId";
|
||||
public static final String TRANSFER_EXTERNAL_ID = "transferExternalId";
|
||||
public static final String TRANSFER_EXTERNAL_GROUP_ID = "transferExternalGroupId";
|
||||
public static final String PURCHASE_PRICE_RATIO = "purchasePriceRatio";
|
||||
public static final String DATEFORMAT = "dateFormat";
|
||||
public static final String LOCALE = "locale";
|
||||
|
||||
+1
-1
@@ -19,5 +19,5 @@
|
||||
package org.apache.fineract.investor.data;
|
||||
|
||||
public enum ExternalTransferStatus {
|
||||
ACTIVE, DECLINED, PENDING, BUYBACK, CANCELLED
|
||||
ACTIVE, ACTIVE_INTERMEDIATE, DECLINED, PENDING, PENDING_INTERMEDIATE, BUYBACK, BUYBACK_INTERMEDIATE, CANCELLED
|
||||
}
|
||||
|
||||
+2
@@ -79,4 +79,6 @@ public class ExternalAssetOwnerTransfer extends AbstractAuditableWithUTCDateTime
|
||||
@Column(name = "external_loan_id", length = 100)
|
||||
private ExternalId externalLoanId;
|
||||
|
||||
@Column(name = "external_group_id", length = 100)
|
||||
private ExternalId externalGroupId;
|
||||
}
|
||||
|
||||
+1
@@ -36,6 +36,7 @@ public class SearchedExternalAssetOwner {
|
||||
|
||||
private final ExternalId owner;
|
||||
private final ExternalId transferExternalId;
|
||||
private final ExternalId transferExternalGroupId;
|
||||
|
||||
private final ExternalTransferStatus status;
|
||||
private final ExternalTransferSubStatus subStatus;
|
||||
|
||||
+5
-4
@@ -94,10 +94,11 @@ public class SearchingExternalAssetOwnerRepositoryImpl implements SearchingExter
|
||||
query.orderBy(orders);
|
||||
|
||||
query.select(cb.construct(SearchedExternalAssetOwner.class, root.get("id"), root.get("loanId"), root.get("externalLoanId"),
|
||||
owner.get("externalId"), root.get("externalId"), root.get("status"), root.get("subStatus"), root.get("purchasePriceRatio"),
|
||||
root.get("settlementDate"), root.get("effectiveDateFrom"), root.get("effectiveDateTo"), details.get("id"),
|
||||
details.get("totalOutstanding"), details.get("totalPrincipalOutstanding"), details.get("totalInterestOutstanding"),
|
||||
details.get("totalFeeChargesOutstanding"), details.get("totalPenaltyChargesOutstanding"), details.get("totalOverpaid")));
|
||||
owner.get("externalId"), root.get("externalId"), root.get("externalGroupId"), root.get("status"), root.get("subStatus"),
|
||||
root.get("purchasePriceRatio"), root.get("settlementDate"), root.get("effectiveDateFrom"), root.get("effectiveDateTo"),
|
||||
details.get("id"), details.get("totalOutstanding"), details.get("totalPrincipalOutstanding"),
|
||||
details.get("totalInterestOutstanding"), details.get("totalFeeChargesOutstanding"),
|
||||
details.get("totalPenaltyChargesOutstanding"), details.get("totalOverpaid")));
|
||||
|
||||
TypedQuery<SearchedExternalAssetOwner> queryToExecute = entityManager.createQuery(query);
|
||||
return criteriaQueryFactory.readPage(queryToExecute, ExternalAssetOwnerTransfer.class, pageable, spec);
|
||||
|
||||
+2
-1
@@ -18,12 +18,13 @@
|
||||
*/
|
||||
package org.apache.fineract.investor.service;
|
||||
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwner;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
|
||||
public interface AccountingService {
|
||||
|
||||
void createJournalEntriesForSaleAssetTransfer(Loan loan, ExternalAssetOwnerTransfer transfer);
|
||||
void createJournalEntriesForSaleAssetTransfer(Loan loan, ExternalAssetOwnerTransfer transfer, ExternalAssetOwner previousOwner);
|
||||
|
||||
void createJournalEntriesForBuybackAssetTransfer(Loan loan, ExternalAssetOwnerTransfer transfer);
|
||||
}
|
||||
|
||||
+79
-22
@@ -31,8 +31,8 @@ import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialA
|
||||
import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
|
||||
import org.apache.fineract.investor.accounting.journalentry.service.InvestorAccountingHelper;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwner;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMapping;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMappingRepository;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
@@ -53,25 +53,44 @@ public class AccountingServiceImpl implements AccountingService {
|
||||
private final ExternalAssetOwnerJournalEntryMappingRepository externalAssetOwnerJournalEntryMappingRepository;
|
||||
private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository;
|
||||
|
||||
private static boolean participateInTransfer(FinancialActivityAccount financialActivityAccount, JournalEntry journalEntry,
|
||||
JournalEntryType filterType) {
|
||||
return filterType.getValue().equals(journalEntry.getType())
|
||||
&& !Objects.equals(financialActivityAccount.getGlAccount().getId(), journalEntry.getGlAccount().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createJournalEntriesForSaleAssetTransfer(final Loan loan, final ExternalAssetOwnerTransfer transfer) {
|
||||
public void createJournalEntriesForSaleAssetTransfer(final Loan loan, final ExternalAssetOwnerTransfer transfer,
|
||||
final ExternalAssetOwner previousOwner) {
|
||||
|
||||
final ExternalAssetOwner newOwner = transfer.getOwner();
|
||||
List<JournalEntry> journalEntryList = createJournalEntries(loan, transfer, true);
|
||||
createMappingToTransfer(transfer, journalEntryList);
|
||||
createMappingToOwner(transfer, journalEntryList, JournalEntryType.DEBIT);
|
||||
|
||||
FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository
|
||||
.findByFinancialActivityTypeWithNotFoundDetection(AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue());
|
||||
journalEntryList.forEach(journalEntry -> {
|
||||
if (isOwnedByFinancialActivityAccount(journalEntry, financialActivityAccount)) {
|
||||
createMappingToOwner(null, journalEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalAssetOwner owner = determineOwnerForSale(journalEntry, loan, previousOwner, newOwner);
|
||||
createMappingToOwner(owner, journalEntry);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createJournalEntriesForBuybackAssetTransfer(final Loan loan, final ExternalAssetOwnerTransfer transfer) {
|
||||
final ExternalAssetOwner previousOwner = transfer.getOwner();
|
||||
List<JournalEntry> journalEntryList = createJournalEntries(loan, transfer, false);
|
||||
createMappingToTransfer(transfer, journalEntryList);
|
||||
createMappingToOwner(transfer, journalEntryList,
|
||||
LoanStatus.OVERPAID.equals(loan.getStatus()) ? JournalEntryType.DEBIT : JournalEntryType.CREDIT);
|
||||
|
||||
FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository
|
||||
.findByFinancialActivityTypeWithNotFoundDetection(AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue());
|
||||
journalEntryList.forEach(journalEntry -> {
|
||||
if (isOwnedByFinancialActivityAccount(journalEntry, financialActivityAccount)) {
|
||||
createMappingToOwner(null, journalEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalAssetOwner owner = determineOwnerForBuyback(journalEntry, loan, previousOwner);
|
||||
createMappingToOwner(owner, journalEntry);
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -95,18 +114,52 @@ public class AccountingServiceImpl implements AccountingService {
|
||||
return journalEntryList;
|
||||
}
|
||||
|
||||
private void createMappingToOwner(ExternalAssetOwnerTransfer transfer, List<JournalEntry> journalEntryList,
|
||||
JournalEntryType filterType) {
|
||||
FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository
|
||||
.findByFinancialActivityTypeWithNotFoundDetection(AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue());
|
||||
journalEntryList.forEach(journalEntry -> {
|
||||
if (participateInTransfer(financialActivityAccount, journalEntry, filterType)) {
|
||||
ExternalAssetOwnerJournalEntryMapping mapping = new ExternalAssetOwnerJournalEntryMapping();
|
||||
mapping.setJournalEntry(journalEntry);
|
||||
mapping.setOwner(transfer.getOwner());
|
||||
externalAssetOwnerJournalEntryMappingRepository.saveAndFlush(mapping);
|
||||
private void createMappingToOwner(final ExternalAssetOwner owner, final JournalEntry journalEntry) {
|
||||
if (owner == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalAssetOwnerJournalEntryMapping mapping = new ExternalAssetOwnerJournalEntryMapping();
|
||||
mapping.setJournalEntry(journalEntry);
|
||||
mapping.setOwner(owner);
|
||||
externalAssetOwnerJournalEntryMappingRepository.saveAndFlush(mapping);
|
||||
}
|
||||
|
||||
private ExternalAssetOwner determineOwnerForSale(final JournalEntry journalEntry, final Loan loan,
|
||||
final ExternalAssetOwner previousOwner, final ExternalAssetOwner newOwner) {
|
||||
final boolean isOverpaid = LoanStatus.OVERPAID.equals(loan.getStatus());
|
||||
|
||||
if (isOverpaid) {
|
||||
if (journalEntry.isCreditEntry()) {
|
||||
return newOwner;
|
||||
}
|
||||
});
|
||||
if (journalEntry.isDebitEntry()) {
|
||||
return previousOwner;
|
||||
}
|
||||
} else {
|
||||
if (journalEntry.isCreditEntry()) {
|
||||
return previousOwner;
|
||||
}
|
||||
if (journalEntry.isDebitEntry()) {
|
||||
return newOwner;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Given journalEntry has invalid type: " + journalEntry.getType());
|
||||
}
|
||||
|
||||
private ExternalAssetOwner determineOwnerForBuyback(final JournalEntry journalEntry, final Loan loan,
|
||||
final ExternalAssetOwner previousOwner) {
|
||||
final boolean isOverpaid = LoanStatus.OVERPAID.equals(loan.getStatus());
|
||||
if (isOverpaid && journalEntry.isDebitEntry()) {
|
||||
return previousOwner;
|
||||
}
|
||||
|
||||
if (!isOverpaid && journalEntry.isCreditEntry()) {
|
||||
return previousOwner;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createMappingToTransfer(ExternalAssetOwnerTransfer transfer, List<JournalEntry> journalEntryList) {
|
||||
@@ -211,4 +264,8 @@ public class AccountingServiceImpl implements AccountingService {
|
||||
}
|
||||
return journalEntryList;
|
||||
}
|
||||
|
||||
private boolean isOwnedByFinancialActivityAccount(JournalEntry journalEntry, FinancialActivityAccount financialActivityAccount) {
|
||||
return Objects.equals(financialActivityAccount.getGlAccount().getId(), journalEntry.getGlAccount().getId());
|
||||
}
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
public interface DelayedSettlementAttributeService {
|
||||
|
||||
boolean isEnabled(Long loanProductId);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import static org.apache.fineract.investor.data.attribute.SettlementModelExternalAssetOwnerLoanProductAttribute.DELAYED_SETTLEMENT;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.investor.data.ExternalTransferLoanProductAttributesData;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DelayedSettlementAttributeServiceImpl implements DelayedSettlementAttributeService {
|
||||
|
||||
private final ExternalAssetOwnerLoanProductAttributesReadService externalAssetOwnerLoanProductAttributesReadService;
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(final Long loanProductId) {
|
||||
Page<ExternalTransferLoanProductAttributesData> attributesDataPage = externalAssetOwnerLoanProductAttributesReadService
|
||||
.retrieveAllLoanProductAttributesByLoanProductId(loanProductId, DELAYED_SETTLEMENT.getAttributeKey());
|
||||
String attributeValue = attributesDataPage.getPageItems().stream().findFirst()
|
||||
.map(ExternalTransferLoanProductAttributesData::getAttributeValue).orElse(null);
|
||||
return DELAYED_SETTLEMENT.getAttributeValue().equals(attributeValue);
|
||||
}
|
||||
}
|
||||
+1
@@ -37,6 +37,7 @@ public interface ExternalAssetOwnersTransferMapper {
|
||||
@Mapping(target = "loan.loanId", source = "loanId")
|
||||
@Mapping(target = "loan.externalId", source = "externalLoanId")
|
||||
@Mapping(target = "transferExternalId", source = "externalId")
|
||||
@Mapping(target = "transferExternalGroupId", source = "externalGroupId")
|
||||
@Mapping(target = "effectiveFrom", source = "effectiveDateFrom")
|
||||
@Mapping(target = "effectiveTo", source = "effectiveDateTo")
|
||||
@Mapping(target = "purchasePriceRatio", source = "purchasePriceRatio")
|
||||
|
||||
+2
@@ -23,6 +23,8 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface ExternalAssetOwnersWriteService {
|
||||
|
||||
CommandProcessingResult intermediarySaleLoanByLoanId(JsonCommand jsonCommand);
|
||||
|
||||
CommandProcessingResult saleLoanByLoanId(JsonCommand command);
|
||||
|
||||
CommandProcessingResult buybackLoanByLoanId(JsonCommand command);
|
||||
|
||||
+238
-35
@@ -18,8 +18,12 @@
|
||||
*/
|
||||
package org.apache.fineract.investor.service;
|
||||
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.ACTIVE_INTERMEDIATE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING_INTERMEDIATE;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.ACTIVE;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.CLOSED_OBLIGATIONS_MET;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.OVERPAID;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.TRANSFER_IN_PROGRESS;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.TRANSFER_ON_HOLD;
|
||||
|
||||
@@ -35,9 +39,10 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndStatus;
|
||||
import org.apache.fineract.cob.data.LoanDataForExternalTransfer;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
@@ -70,26 +75,55 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
|
||||
private static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||
private static final List<LoanStatus> ACTIVE_LOAN_STATUSES = List.of(ACTIVE, TRANSFER_IN_PROGRESS, TRANSFER_ON_HOLD);
|
||||
private static final List<LoanStatus> VALID_DELAYED_SETTLEMENT_LOAN_STATUSES_BUYBACK_AND_SALE = List.of(ACTIVE, TRANSFER_IN_PROGRESS,
|
||||
TRANSFER_ON_HOLD, OVERPAID, CLOSED_OBLIGATIONS_MET);
|
||||
private static final List<ExternalTransferStatus> BUYBACK_READY_STATUSES = List.of(ExternalTransferStatus.PENDING,
|
||||
ExternalTransferStatus.ACTIVE);
|
||||
private static final List<ExternalTransferStatus> BUYBACK_READY_STATUSES_FOR_DELAY_SETTLEMENT = List
|
||||
.of(ExternalTransferStatus.ACTIVE_INTERMEDIATE, ExternalTransferStatus.ACTIVE);
|
||||
private final ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
private final ExternalAssetOwnerRepository externalAssetOwnerRepository;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanRepository loanRepository;
|
||||
private final DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult intermediarySaleLoanByLoanId(JsonCommand command) {
|
||||
final JsonElement json = fromApiJsonHelper.parse(command.json());
|
||||
validateIntermediarySaleRequestBody(command.json());
|
||||
Long loanId = command.getLoanId();
|
||||
LoanDataForExternalTransfer loanDataForExternalTransfer = fetchAndValidateLoanDataForExternalTransfer(loanId);
|
||||
if (!delayedSettlementAttributeService.isEnabled(loanDataForExternalTransfer.getLoanProductId())) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
String.format("Delayed Settlement Configuration is not enabled for the loan product: %s",
|
||||
loanDataForExternalTransfer.getLoanProductShortName()));
|
||||
}
|
||||
ExternalId externalId = getTransferExternalIdFromJson(json);
|
||||
validateExternalId(externalId);
|
||||
validateLoanStatusIntermediarySale(loanDataForExternalTransfer);
|
||||
ExternalAssetOwnerTransfer intermediarySaleTransfer = createIntermediarySaleTransfer(loanId, json,
|
||||
loanDataForExternalTransfer.getExternalId());
|
||||
validateIntermediarySale(intermediarySaleTransfer);
|
||||
externalAssetOwnerTransferRepository.saveAndFlush(intermediarySaleTransfer);
|
||||
return buildResponseData(intermediarySaleTransfer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult saleLoanByLoanId(JsonCommand command) {
|
||||
final JsonElement json = fromApiJsonHelper.parse(command.json());
|
||||
final LoanDataForExternalTransfer loanDataForExternalTransfer = fetchAndValidateLoanDataForExternalTransfer(command.getLoanId());
|
||||
final boolean isDelayedSettlementEnabled = delayedSettlementAttributeService
|
||||
.isEnabled(loanDataForExternalTransfer.getLoanProductId());
|
||||
validateSaleRequestBody(command.json());
|
||||
ExternalId externalId = getTransferExternalIdFromJson(json);
|
||||
validateExternalId(externalId);
|
||||
Long loanId = command.getLoanId();
|
||||
LoanIdAndExternalIdAndStatus loanIdAndExternalIdAndStatus = fetchLoanDetails(loanId);
|
||||
validateLoanStatus(loanIdAndExternalIdAndStatus);
|
||||
validateLoanStatus(loanDataForExternalTransfer, isDelayedSettlementEnabled);
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = createSaleTransfer(loanId, json,
|
||||
loanIdAndExternalIdAndStatus.getExternalId());
|
||||
validateSale(externalAssetOwnerTransfer);
|
||||
loanDataForExternalTransfer.getExternalId());
|
||||
validateSale(externalAssetOwnerTransfer, isDelayedSettlementEnabled);
|
||||
externalAssetOwnerTransferRepository.saveAndFlush(externalAssetOwnerTransfer);
|
||||
return buildResponseData(externalAssetOwnerTransfer);
|
||||
}
|
||||
@@ -99,13 +133,13 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
public CommandProcessingResult buybackLoanByLoanId(JsonCommand command) {
|
||||
final JsonElement json = fromApiJsonHelper.parse(command.json());
|
||||
validateBuybackRequestBody(command.json());
|
||||
Long loanId = command.getLoanId();
|
||||
validateLoan(loanId);
|
||||
LoanDataForExternalTransfer loanDataForExternalTransfer = fetchAndValidateLoanDataForExternalTransfer(command.getLoanId());
|
||||
LocalDate settlementDate = getSettlementDateFromJson(json);
|
||||
ExternalId externalId = getTransferExternalIdFromJson(json);
|
||||
validateSettlementDate(settlementDate);
|
||||
validateExternalId(externalId);
|
||||
ExternalAssetOwnerTransfer effectiveTransfer = fetchAndValidateEffectiveTransferForBuyback(loanId, settlementDate);
|
||||
ExternalAssetOwnerTransfer effectiveTransfer = fetchAndValidateEffectiveTransferForBuyback(loanDataForExternalTransfer,
|
||||
settlementDate);
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = createBuybackTransfer(effectiveTransfer, settlementDate, externalId);
|
||||
externalAssetOwnerTransferRepository.saveAndFlush(externalAssetOwnerTransfer);
|
||||
return buildResponseData(externalAssetOwnerTransfer);
|
||||
@@ -120,10 +154,8 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLoan(Long loanId) {
|
||||
if (!loanRepository.existsById(loanId)) {
|
||||
throw new LoanNotFoundException(loanId);
|
||||
}
|
||||
private LoanDataForExternalTransfer fetchAndValidateLoanDataForExternalTransfer(Long loanId) {
|
||||
return loanRepository.findLoanDataForExternalTransferByLoanId(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,10 +168,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
return buildResponseData(cancelTransfer);
|
||||
}
|
||||
|
||||
private void validateEffectiveTransferForSale(final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
List<ExternalAssetOwnerTransfer> effectiveTransfers = externalAssetOwnerTransferRepository
|
||||
.findEffectiveTransfersOrderByIdDesc(externalAssetOwnerTransfer.getLoanId(), DateUtils.getBusinessLocalDate());
|
||||
|
||||
private void validateEffectiveTransferForSale(final List<ExternalAssetOwnerTransfer> effectiveTransfers) {
|
||||
if (effectiveTransfers.size() == 2) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException("This loan cannot be sold, there is already an in progress transfer");
|
||||
} else if (effectiveTransfers.size() == 1) {
|
||||
@@ -156,9 +185,47 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
}
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer fetchAndValidateEffectiveTransferForBuyback(final Long loanId, final LocalDate settlementDate) {
|
||||
private void validateEffectiveTransferForDelayedSettlementSale(final List<ExternalAssetOwnerTransfer> effectiveTransfers) {
|
||||
if (effectiveTransfers.size() > 1) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException("This loan cannot be sold, there is already an in progress transfer");
|
||||
} else if (effectiveTransfers.size() == 1) {
|
||||
if (!ACTIVE_INTERMEDIATE.equals(effectiveTransfers.get(0).getStatus())) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"This loan cannot be sold, because it is not in ACTIVE-INTERMEDIATE state.");
|
||||
}
|
||||
} else {
|
||||
throw new ExternalAssetOwnerInitiateTransferException("This loan cannot be sold, no effective transfer found.");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateEffectiveTransferForIntermediarySale(final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
List<ExternalAssetOwnerTransfer> effectiveTransfers = externalAssetOwnerTransferRepository
|
||||
.findEffectiveTransfersOrderByIdDesc(loanId, DateUtils.getBusinessLocalDate());
|
||||
.findEffectiveTransfersOrderByIdDesc(externalAssetOwnerTransfer.getLoanId(), DateUtils.getBusinessLocalDate());
|
||||
|
||||
if (effectiveTransfers.size() > 1) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException("This loan cannot be sold, there is already an in progress transfer");
|
||||
} else if (effectiveTransfers.size() == 1) {
|
||||
if (PENDING_INTERMEDIATE.equals(effectiveTransfers.get(0).getStatus())) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"External asset owner transfer is already in PENDING_INTERMEDIATE state for this loan");
|
||||
} else if (ExternalTransferStatus.ACTIVE.equals(effectiveTransfers.get(0).getStatus())) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"This loan cannot be sold, because it is owned by an external asset owner");
|
||||
} else {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(String.format(
|
||||
"This loan cannot be sold, because it is incorrect state! (transferId = %s)", effectiveTransfers.get(0).getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer fetchAndValidateEffectiveTransferForBuyback(
|
||||
final LoanDataForExternalTransfer loanDataForExternalTransfer, final LocalDate settlementDate) {
|
||||
if (delayedSettlementAttributeService.isEnabled(loanDataForExternalTransfer.getLoanProductId())) {
|
||||
return fetchAndValidateEffectiveTransferForBuybackWithDelayedSettlement(loanDataForExternalTransfer, settlementDate);
|
||||
}
|
||||
|
||||
List<ExternalAssetOwnerTransfer> effectiveTransfers = externalAssetOwnerTransferRepository
|
||||
.findEffectiveTransfersOrderByIdDesc(loanDataForExternalTransfer.getId(), DateUtils.getBusinessLocalDate());
|
||||
|
||||
if (effectiveTransfers.size() == 0) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
@@ -179,6 +246,39 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
return effectiveTransfers.get(0);
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer fetchAndValidateEffectiveTransferForBuybackWithDelayedSettlement(
|
||||
final LoanDataForExternalTransfer loanDataForExternalTransfer, final LocalDate settlementDate) {
|
||||
List<ExternalAssetOwnerTransfer> effectiveTransfers = externalAssetOwnerTransferRepository
|
||||
.findEffectiveTransfersOrderByIdDesc(loanDataForExternalTransfer.getId(), DateUtils.getBusinessLocalDate());
|
||||
|
||||
if (effectiveTransfers.isEmpty()) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"This loan cannot be bought back, it is not owned by an external asset owner");
|
||||
}
|
||||
|
||||
Set<ExternalTransferStatus> effectiveTransferStatuses = effectiveTransfers.stream().map(ExternalAssetOwnerTransfer::getStatus)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (Set.of(ExternalTransferStatus.ACTIVE_INTERMEDIATE, ExternalTransferStatus.PENDING).equals(effectiveTransferStatuses)) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException("This loan cannot be bought back, external asset owner sale is pending");
|
||||
} else if (Set.of(ExternalTransferStatus.ACTIVE_INTERMEDIATE, ExternalTransferStatus.BUYBACK_INTERMEDIATE)
|
||||
.equals(effectiveTransferStatuses)
|
||||
|| Set.of(ExternalTransferStatus.ACTIVE, ExternalTransferStatus.BUYBACK).equals(effectiveTransferStatuses)) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(
|
||||
"This loan cannot be bought back, external asset owner buyback transfer is already in progress");
|
||||
} else if (!BUYBACK_READY_STATUSES_FOR_DELAY_SETTLEMENT.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 (DateUtils.isBefore(settlementDate, 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 fetchAndValidateEffectiveTransferForCancel(final Long transferId) {
|
||||
ExternalAssetOwnerTransfer selectedTransfer = externalAssetOwnerTransferRepository.findById(transferId)
|
||||
.orElseThrow(() -> new ExternalAssetOwnerInitiateTransferException(
|
||||
@@ -206,10 +306,9 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
externalAssetOwnerTransfer.setExternalId(externalId);
|
||||
externalAssetOwnerTransfer.setOwner(effectiveTransfer.getOwner());
|
||||
externalAssetOwnerTransfer.setStatus(ExternalTransferStatus.BUYBACK);
|
||||
externalAssetOwnerTransfer.setStatus(determineStatusAfterBuyback(effectiveTransfer));
|
||||
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);
|
||||
@@ -217,6 +316,16 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
return externalAssetOwnerTransfer;
|
||||
}
|
||||
|
||||
private ExternalTransferStatus determineStatusAfterBuyback(ExternalAssetOwnerTransfer effectiveTransfer) {
|
||||
return switch (effectiveTransfer.getStatus()) {
|
||||
case PENDING -> ExternalTransferStatus.BUYBACK;
|
||||
case ACTIVE -> ExternalTransferStatus.BUYBACK;
|
||||
case ACTIVE_INTERMEDIATE -> ExternalTransferStatus.BUYBACK_INTERMEDIATE;
|
||||
default -> throw new ExternalAssetOwnerInitiateTransferException(String.format(
|
||||
"This loan cannot be bought back, effective transfer is not in right state: %s", effectiveTransfer.getStatus()));
|
||||
};
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer createCancelTransfer(ExternalAssetOwnerTransfer effectiveTransfer) {
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
externalAssetOwnerTransfer.setExternalId(effectiveTransfer.getExternalId());
|
||||
@@ -224,6 +333,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
externalAssetOwnerTransfer.setSubStatus(ExternalTransferSubStatus.USER_REQUESTED);
|
||||
externalAssetOwnerTransfer.setLoanId(effectiveTransfer.getLoanId());
|
||||
externalAssetOwnerTransfer.setExternalLoanId(effectiveTransfer.getExternalLoanId());
|
||||
externalAssetOwnerTransfer.setExternalGroupId(effectiveTransfer.getExternalGroupId());
|
||||
externalAssetOwnerTransfer.setOwner(effectiveTransfer.getOwner());
|
||||
externalAssetOwnerTransfer.setSettlementDate(effectiveTransfer.getSettlementDate());
|
||||
externalAssetOwnerTransfer.setEffectiveDateFrom(effectiveTransfer.getEffectiveDateFrom());
|
||||
@@ -241,9 +351,22 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
.build();
|
||||
}
|
||||
|
||||
private void validateSale(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
private void validateSale(ExternalAssetOwnerTransfer externalAssetOwnerTransfer, boolean isDelayedSettlementEnabled) {
|
||||
validateSettlementDate(externalAssetOwnerTransfer);
|
||||
validateEffectiveTransferForSale(externalAssetOwnerTransfer);
|
||||
|
||||
final List<ExternalAssetOwnerTransfer> effectiveTransfers = externalAssetOwnerTransferRepository
|
||||
.findEffectiveTransfersOrderByIdDesc(externalAssetOwnerTransfer.getLoanId(), DateUtils.getBusinessLocalDate());
|
||||
|
||||
if (isDelayedSettlementEnabled) {
|
||||
validateEffectiveTransferForDelayedSettlementSale(effectiveTransfers);
|
||||
} else {
|
||||
validateEffectiveTransferForSale(effectiveTransfers);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIntermediarySale(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
validateSettlementDate(externalAssetOwnerTransfer);
|
||||
validateEffectiveTransferForIntermediarySale(externalAssetOwnerTransfer);
|
||||
}
|
||||
|
||||
private void validateSettlementDate(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
@@ -256,9 +379,25 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLoanStatus(LoanIdAndExternalIdAndStatus entity) {
|
||||
if (!ACTIVE_LOAN_STATUSES.contains(LoanStatus.fromInt(entity.getLoanStatus()))) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException("Loan is not in active status");
|
||||
private void validateLoanStatus(LoanDataForExternalTransfer loanDataForExternalTransfer, boolean isDelayedSettlementEnabled) {
|
||||
LoanStatus loanStatus = LoanStatus.fromInt(loanDataForExternalTransfer.getLoanStatus());
|
||||
if (!getValidLoanStatusList(isDelayedSettlementEnabled).contains(loanStatus)) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(String.format("Loan status %s is not valid for transfer.", loanStatus));
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLoanStatusIntermediarySale(LoanDataForExternalTransfer loanDataForExternalTransfer) {
|
||||
LoanStatus loanStatus = LoanStatus.fromInt(loanDataForExternalTransfer.getLoanStatus());
|
||||
if (!ACTIVE_LOAN_STATUSES.contains(loanStatus)) {
|
||||
throw new ExternalAssetOwnerInitiateTransferException(String.format("Loan status %s is not valid for transfer.", loanStatus));
|
||||
}
|
||||
}
|
||||
|
||||
private List<LoanStatus> getValidLoanStatusList(boolean isDelayedSettlementEnabled) {
|
||||
if (isDelayedSettlementEnabled) {
|
||||
return VALID_DELAYED_SETTLEMENT_LOAN_STATUSES_BUYBACK_AND_SALE;
|
||||
} else {
|
||||
return ACTIVE_LOAN_STATUSES;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,14 +415,33 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
externalAssetOwnerTransfer.setEffectiveDateTo(FUTURE_DATE_9999_12_31);
|
||||
externalAssetOwnerTransfer.setLoanId(loanId);
|
||||
externalAssetOwnerTransfer.setExternalLoanId(externalLoanId);
|
||||
externalAssetOwnerTransfer.setExternalGroupId(getTransferExternalGroupIdFromJson(json));
|
||||
return externalAssetOwnerTransfer;
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer createIntermediarySaleTransfer(Long loanId, JsonElement json, ExternalId externalLoanId) {
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
LocalDate effectiveFrom = ThreadLocalContextUtil.getBusinessDate();
|
||||
|
||||
ExternalAssetOwner owner = getOwner(json);
|
||||
externalAssetOwnerTransfer.setOwner(owner);
|
||||
externalAssetOwnerTransfer.setExternalId(getTransferExternalIdFromJson(json));
|
||||
externalAssetOwnerTransfer.setStatus(PENDING_INTERMEDIATE);
|
||||
externalAssetOwnerTransfer.setPurchasePriceRatio(getPurchasePriceRatioFromJson(json));
|
||||
externalAssetOwnerTransfer.setSettlementDate(getSettlementDateFromJson(json));
|
||||
externalAssetOwnerTransfer.setEffectiveDateFrom(effectiveFrom);
|
||||
externalAssetOwnerTransfer.setEffectiveDateTo(FUTURE_DATE_9999_12_31);
|
||||
externalAssetOwnerTransfer.setLoanId(loanId);
|
||||
externalAssetOwnerTransfer.setExternalLoanId(externalLoanId);
|
||||
externalAssetOwnerTransfer.setExternalGroupId(getTransferExternalGroupIdFromJson(json));
|
||||
return externalAssetOwnerTransfer;
|
||||
}
|
||||
|
||||
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,
|
||||
ExternalTransferRequestParameters.DATEFORMAT, ExternalTransferRequestParameters.LOCALE));
|
||||
final Set<String> requestParameters = new HashSet<>(Arrays.asList(ExternalTransferRequestParameters.SETTLEMENT_DATE,
|
||||
ExternalTransferRequestParameters.OWNER_EXTERNAL_ID, ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID,
|
||||
ExternalTransferRequestParameters.TRANSFER_EXTERNAL_GROUP_ID, ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO,
|
||||
ExternalTransferRequestParameters.DATEFORMAT, ExternalTransferRequestParameters.LOCALE));
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
@@ -308,6 +466,51 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
LocalDate settlementDate = fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE, json);
|
||||
baseDataValidator.reset().parameter(ExternalTransferRequestParameters.SETTLEMENT_DATE).value(settlementDate).notNull();
|
||||
|
||||
final String transferExternalGroupId = fromApiJsonHelper
|
||||
.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_GROUP_ID, json);
|
||||
baseDataValidator.reset().parameter(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_GROUP_ID).value(transferExternalGroupId)
|
||||
.ignoreIfNull().notExceedingLengthOf(100);
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIntermediarySaleRequestBody(String apiRequestBodyAsJson) {
|
||||
final Set<String> requestParameters = new HashSet<>(Arrays.asList(ExternalTransferRequestParameters.SETTLEMENT_DATE,
|
||||
ExternalTransferRequestParameters.OWNER_EXTERNAL_ID, ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID,
|
||||
ExternalTransferRequestParameters.TRANSFER_EXTERNAL_GROUP_ID, ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO,
|
||||
ExternalTransferRequestParameters.DATEFORMAT, ExternalTransferRequestParameters.LOCALE));
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, apiRequestBodyAsJson, requestParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loantransfer");
|
||||
final JsonElement json = fromApiJsonHelper.parse(apiRequestBodyAsJson);
|
||||
|
||||
String ownerExternalId = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID, json);
|
||||
baseDataValidator.reset().parameter(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID).value(ownerExternalId).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
|
||||
String transferExternalId = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, json);
|
||||
baseDataValidator.reset().parameter(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID).value(transferExternalId).ignoreIfNull()
|
||||
.notExceedingLengthOf(100);
|
||||
|
||||
String purchasePriceRatio = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO, json);
|
||||
baseDataValidator.reset().parameter(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO).value(purchasePriceRatio).notBlank()
|
||||
.notExceedingLengthOf(50);
|
||||
|
||||
LocalDate settlementDate = fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE, json);
|
||||
baseDataValidator.reset().parameter(ExternalTransferRequestParameters.SETTLEMENT_DATE).value(settlementDate).notNull();
|
||||
|
||||
String transferExternalGroupId = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_GROUP_ID,
|
||||
json);
|
||||
baseDataValidator.reset().parameter(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_GROUP_ID).value(transferExternalGroupId)
|
||||
.ignoreIfNull().notExceedingLengthOf(100);
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
@@ -352,6 +555,12 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
return StringUtils.isEmpty(transferExternalId) ? ExternalId.generate() : ExternalIdFactory.produce(transferExternalId);
|
||||
}
|
||||
|
||||
private ExternalId getTransferExternalGroupIdFromJson(JsonElement json) {
|
||||
String transferExternalGroupId = fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_GROUP_ID,
|
||||
json);
|
||||
return StringUtils.isEmpty(transferExternalGroupId) ? null : ExternalIdFactory.produce(transferExternalGroupId);
|
||||
}
|
||||
|
||||
private String getPurchasePriceRatioFromJson(JsonElement json) {
|
||||
return fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO, json);
|
||||
}
|
||||
@@ -368,10 +577,4 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
@CommandType(entity = "LOAN", action = "INTERMEDIARYSALE")
|
||||
public class IntermediarySaleToExternalAssetOwnerHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final ExternalAssetOwnersWriteService externalAssetOwnersWriteService;
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(JsonCommand command) {
|
||||
return externalAssetOwnersWriteService.intermediarySaleLoanByLoanId(command);
|
||||
}
|
||||
|
||||
}
|
||||
+20
-19
@@ -19,10 +19,14 @@
|
||||
package org.apache.fineract.investor.service;
|
||||
|
||||
import static org.apache.fineract.infrastructure.core.service.DateUtils.getBusinessLocalDate;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.ACTIVE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.ACTIVE_INTERMEDIATE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.BUYBACK;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.BUYBACK_INTERMEDIATE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.CANCELLED;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.DECLINED;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING_INTERMEDIATE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferSubStatus.BALANCE_NEGATIVE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferSubStatus.BALANCE_ZERO;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferSubStatus.SAMEDAY_TRANSFERS;
|
||||
@@ -37,7 +41,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
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.data.ExternalTransferStatus;
|
||||
import org.apache.fineract.investor.data.ExternalTransferSubStatus;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferDetails;
|
||||
@@ -64,25 +67,21 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
|
||||
@Override
|
||||
public void handleLoanClosedOrOverpaid(Loan loan) {
|
||||
Long loanId = loan.getId();
|
||||
List<ExternalAssetOwnerTransfer> transferDataList = findAllPendingOrBuybackTransfers(loanId);
|
||||
List<ExternalAssetOwnerTransfer> transferDataList = findAllPendingOrBuybackOrIntermediateTransfers(loanId);
|
||||
|
||||
if (transferDataList.size() == 2) {
|
||||
ExternalTransferSubStatus subStatus;
|
||||
ExternalAssetOwnerTransfer pendingSaleTransfer = transferDataList.get(0);
|
||||
ExternalAssetOwnerTransfer pendingBuybackTransfer = transferDataList.get(1);
|
||||
if (transferDataList.size() > 1) {
|
||||
if (isSameDayTransfers(transferDataList)) {
|
||||
subStatus = SAMEDAY_TRANSFERS;
|
||||
cancelTransfer(loan, pendingSaleTransfer, subStatus);
|
||||
cancelTransfer(loan, pendingBuybackTransfer, subStatus);
|
||||
transferDataList.forEach(externalAssetOwnerTransfer -> cancelTransfer(loan, externalAssetOwnerTransfer, SAMEDAY_TRANSFERS));
|
||||
} else {
|
||||
declineTransfer(loan, pendingSaleTransfer);
|
||||
cancelTransfer(loan, pendingBuybackTransfer, UNSOLD);
|
||||
// decline first and cancel the rest
|
||||
declineTransfer(loan, transferDataList.get(0));
|
||||
transferDataList.stream().skip(1).forEach(assetOwnerTransfer -> cancelTransfer(loan, assetOwnerTransfer, UNSOLD));
|
||||
}
|
||||
} else if (transferDataList.size() == 1) {
|
||||
ExternalAssetOwnerTransfer transfer = transferDataList.get(0);
|
||||
if (PENDING.equals(transfer.getStatus())) {
|
||||
if (PENDING.equals(transfer.getStatus()) || PENDING_INTERMEDIATE.equals(transfer.getStatus())) {
|
||||
declineTransfer(loan, transfer);
|
||||
} else if (BUYBACK.equals(transfer.getStatus())) {
|
||||
} else if (BUYBACK.equals(transfer.getStatus()) || BUYBACK_INTERMEDIATE.equals(transfer.getStatus())) {
|
||||
executePendingBuybackTransfer(loan, transfer);
|
||||
}
|
||||
}
|
||||
@@ -103,7 +102,7 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
|
||||
}
|
||||
|
||||
private void executePendingBuybackTransfer(final Loan loan, ExternalAssetOwnerTransfer buybackTransfer) {
|
||||
ExternalAssetOwnerTransfer activeTransfer = findActiveTransfer(loan, buybackTransfer);
|
||||
ExternalAssetOwnerTransfer activeTransfer = findActiveOrActiveIntermediateTransfer(loan, buybackTransfer);
|
||||
updateActiveTransfer(activeTransfer);
|
||||
buybackTransfer = updatePendingBuybackTransfer(loan, buybackTransfer);
|
||||
|
||||
@@ -119,6 +118,7 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
|
||||
ExternalAssetOwnerTransfer cancelledTransfer = new ExternalAssetOwnerTransfer();
|
||||
cancelledTransfer.setOwner(pendingTransfer.getOwner());
|
||||
cancelledTransfer.setExternalId(pendingTransfer.getExternalId());
|
||||
cancelledTransfer.setExternalGroupId(pendingTransfer.getExternalGroupId());
|
||||
cancelledTransfer.setStatus(CANCELLED);
|
||||
cancelledTransfer.setSubStatus(subStatus);
|
||||
cancelledTransfer.setSettlementDate(pendingTransfer.getSettlementDate());
|
||||
@@ -134,6 +134,7 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
|
||||
ExternalAssetOwnerTransfer declinedTransfer = new ExternalAssetOwnerTransfer();
|
||||
declinedTransfer.setOwner(pendingSaleTransfer.getOwner());
|
||||
declinedTransfer.setExternalId(pendingSaleTransfer.getExternalId());
|
||||
declinedTransfer.setExternalGroupId(pendingSaleTransfer.getExternalGroupId());
|
||||
declinedTransfer.setStatus(DECLINED);
|
||||
declinedTransfer.setSubStatus(isBiggerThanZero(loan.getTotalOverpaid()) ? BALANCE_NEGATIVE : BALANCE_ZERO);
|
||||
declinedTransfer.setSettlementDate(pendingSaleTransfer.getSettlementDate());
|
||||
@@ -176,20 +177,20 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
|
||||
return details;
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransfer findActiveTransfer(Loan loan, ExternalAssetOwnerTransfer buybackTransfer) {
|
||||
private ExternalAssetOwnerTransfer findActiveOrActiveIntermediateTransfer(Loan loan, ExternalAssetOwnerTransfer buybackTransfer) {
|
||||
return externalAssetOwnerTransferRepository
|
||||
.findOne((root, query, criteriaBuilder) -> criteriaBuilder.and(criteriaBuilder.equal(root.get("loanId"), loan.getId()),
|
||||
criteriaBuilder.equal(root.get("owner"), buybackTransfer.getOwner()),
|
||||
criteriaBuilder.equal(root.get("status"), ExternalTransferStatus.ACTIVE),
|
||||
root.get("status").in(List.of(ACTIVE, ACTIVE_INTERMEDIATE)),
|
||||
criteriaBuilder.equal(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31)))
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private List<ExternalAssetOwnerTransfer> findAllPendingOrBuybackTransfers(Long loanId) {
|
||||
private List<ExternalAssetOwnerTransfer> findAllPendingOrBuybackOrIntermediateTransfers(Long loanId) {
|
||||
return externalAssetOwnerTransferRepository
|
||||
.findAll(
|
||||
(root, query, criteriaBuilder) -> criteriaBuilder.and(criteriaBuilder.equal(root.get("loanId"), loanId),
|
||||
root.get("status").in(List.of(PENDING, BUYBACK)),
|
||||
root.get("status").in(List.of(PENDING, BUYBACK, PENDING_INTERMEDIATE, BUYBACK_INTERMEDIATE)),
|
||||
criteriaBuilder.equal(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31)),
|
||||
Sort.by(Sort.Direction.ASC, "id"));
|
||||
}
|
||||
@@ -199,6 +200,6 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
|
||||
}
|
||||
|
||||
private static boolean isSameDayTransfers(List<ExternalAssetOwnerTransfer> transferDataList) {
|
||||
return Objects.equals(transferDataList.get(0).getSettlementDate(), transferDataList.get(1).getSettlementDate());
|
||||
return (transferDataList.stream().map(ExternalAssetOwnerTransfer::getSettlementDate).distinct().count() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import org.apache.fineract.investor.data.ExternalTransferSubStatus;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
|
||||
public interface LoanTransferabilityService {
|
||||
|
||||
boolean isTransferable(Loan loan, ExternalAssetOwnerTransfer externalAssetOwnerTransfer);
|
||||
|
||||
ExternalTransferSubStatus getDeclinedSubStatus(Loan loan);
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
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.portfolio.loanaccount.domain.Loan;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class LoanTransferabilityServiceImpl implements LoanTransferabilityService {
|
||||
|
||||
private final DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||
|
||||
@Override
|
||||
public boolean isTransferable(final Loan loan, final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
if (shouldValidateTransferable(loan, externalAssetOwnerTransfer)) {
|
||||
return MathUtil.nullToDefault(loan.getSummary().getTotalOutstanding(), BigDecimal.ZERO).compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExternalTransferSubStatus getDeclinedSubStatus(final Loan loan) {
|
||||
if (MathUtil.nullToDefault(loan.getTotalOverpaid(), BigDecimal.ZERO).compareTo(BigDecimal.ZERO) > 0) {
|
||||
return ExternalTransferSubStatus.BALANCE_NEGATIVE;
|
||||
}
|
||||
|
||||
return ExternalTransferSubStatus.BALANCE_ZERO;
|
||||
}
|
||||
|
||||
private boolean shouldValidateTransferable(final Loan loan, final ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
if (!delayedSettlementAttributeService.isEnabled(loan.getLoanProduct().getId())) {
|
||||
// When delayed settlement is disabled, asset is directly sold to investor. Need to validate.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ExternalTransferStatus.PENDING_INTERMEDIATE == externalAssetOwnerTransfer.getStatus()) {
|
||||
// When delayed settlement is enabled and asset is sold to intermediate. Need to validate.
|
||||
return true;
|
||||
}
|
||||
|
||||
// When delayed settlement is enabled and asset is sold from intermediate to investor. No need to validate.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+18
-2
@@ -20,14 +20,18 @@ package org.apache.fineract.investor.service.serialization.serializer.investor;
|
||||
|
||||
import static org.apache.fineract.infrastructure.core.service.DateUtils.DEFAULT_DATE_FORMATTER;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.ACTIVE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.ACTIVE_INTERMEDIATE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.BUYBACK;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.BUYBACK_INTERMEDIATE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.CANCELLED;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.DECLINED;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING_INTERMEDIATE;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.avro.generic.GenericContainer;
|
||||
import org.apache.fineract.avro.generator.ByteBufferSerializable;
|
||||
@@ -50,6 +54,9 @@ import org.springframework.stereotype.Component;
|
||||
@RequiredArgsConstructor
|
||||
public class InvestorBusinessEventSerializer implements BusinessEventSerializer {
|
||||
|
||||
private static final Set<ExternalTransferStatus> EXECUTED_TRANSFER_STATUSES = Set.of(ACTIVE, ACTIVE_INTERMEDIATE, BUYBACK,
|
||||
BUYBACK_INTERMEDIATE);
|
||||
|
||||
private final ExternalAssetOwnersReadService externalAssetOwnersReadService;
|
||||
|
||||
private static CurrencyDataV1 getCurrencyFromEvent(InvestorBusinessEvent event) {
|
||||
@@ -83,6 +90,7 @@ public class InvestorBusinessEventSerializer implements BusinessEventSerializer
|
||||
LoanOwnershipTransferDataV1.Builder builder = LoanOwnershipTransferDataV1.newBuilder().setLoanId(transferData.getLoan().getLoanId())
|
||||
.setLoanExternalId(transferData.getLoan().getExternalId()).setTransferExternalId(transferData.getTransferExternalId())
|
||||
.setAssetOwnerExternalId(transferData.getOwner().getExternalId())
|
||||
.setTransferExternalGroupId(transferData.getTransferExternalGroupId())
|
||||
.setPurchasePriceRatio(transferData.getPurchasePriceRatio()).setCurrency(getCurrencyFromEvent(event))
|
||||
.setSettlementDate(transferData.getSettlementDate().format(DEFAULT_DATE_FORMATTER))
|
||||
.setSubmittedDate(transferData.getSettlementDate().format(DEFAULT_DATE_FORMATTER)).setType(transferType)
|
||||
@@ -103,7 +111,15 @@ public class InvestorBusinessEventSerializer implements BusinessEventSerializer
|
||||
|
||||
@NotNull
|
||||
private static String getType(ExternalTransferStatus transferStatus) {
|
||||
return transferStatus == BUYBACK ? "BUYBACK" : "SALE";
|
||||
if (transferStatus == BUYBACK || transferStatus == BUYBACK_INTERMEDIATE) {
|
||||
return "BUYBACK";
|
||||
}
|
||||
|
||||
if (transferStatus == ACTIVE_INTERMEDIATE || transferStatus == PENDING_INTERMEDIATE) {
|
||||
return "INTERMEDIARYSALE";
|
||||
}
|
||||
|
||||
return "SALE";
|
||||
}
|
||||
|
||||
private List<UnpaidChargeDataV1> getUnpaidChargeData(InvestorBusinessEvent event) {
|
||||
@@ -126,7 +142,7 @@ public class InvestorBusinessEventSerializer implements BusinessEventSerializer
|
||||
}
|
||||
|
||||
private String getStatus(ExternalTransferStatus status) {
|
||||
if (ACTIVE.equals(status) || BUYBACK.equals(status)) {
|
||||
if (EXECUTED_TRANSFER_STATUSES.contains(status)) {
|
||||
return "EXECUTED";
|
||||
} else if (DECLINED.equals(status) || CANCELLED.equals(status)) {
|
||||
return status.name();
|
||||
|
||||
+2
@@ -36,4 +36,6 @@
|
||||
<include relativeToChangelogFile="true" file="parts/0012_add_external_asset_owner_transfer_index.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/0013_add_additional_asset_owner_indices.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/0014_add_external_asset_owner_loan_product_configurable_attributes.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/0015_add_intermediary_sale_command.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/0016_add_external_reference_id.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?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 id="1" author="fineract">
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="transaction_loan"/>
|
||||
<column name="code" value="INTERMEDIARYSALE_LOAN"/>
|
||||
<column name="entity_name" value="LOAN"/>
|
||||
<column name="action_name" value="INTERMEDIARYSALE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?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 id="1" author="fineract">
|
||||
<addColumn tableName="m_external_asset_owner_transfer">
|
||||
<column name="external_group_id" type="VARCHAR(100)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user