FINERACT-1926: Fix wrong type of transfer in cancellation event + fix NPE

This commit is contained in:
Adam Saghy
2023-06-14 21:10:24 +02:00
parent 78d9a0201e
commit eacf28192a
8 changed files with 69 additions and 44 deletions
@@ -28,6 +28,7 @@ import org.apache.fineract.cob.loan.LoanCOBBusinessStep;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccountSnapshotBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.interoperation.util.MathUtil;
import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition;
import org.apache.fineract.investor.data.ExternalTransferStatus;
import org.apache.fineract.investor.data.ExternalTransferSubStatus;
@@ -135,7 +136,7 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
accountingService.createJournalEntriesForSaleAssetTransfer(loan, newExternalAssetOwnerTransfer);
} else {
ExternalTransferSubStatus subStatus = ExternalTransferSubStatus.BALANCE_ZERO;
if (loan.getTotalOverpaid().compareTo(BigDecimal.ZERO) > 0) {
if (MathUtil.nullToDefault(loan.getTotalOverpaid(), BigDecimal.ZERO).compareTo(BigDecimal.ZERO) > 0) {
subStatus = ExternalTransferSubStatus.BALANCE_NEGATIVE;
}
newExternalAssetOwnerTransfer = createNewEntry(settlementDate, externalAssetOwnerTransfer, ExternalTransferStatus.DECLINED,
@@ -169,7 +170,7 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
}
private boolean isTransferable(final Loan loan) {
return loan.getLoanSummary().getTotalOutstanding().compareTo(BigDecimal.ZERO) > 0;
return MathUtil.nullToDefault(loan.getLoanSummary().getTotalOutstanding(), BigDecimal.ZERO).compareTo(BigDecimal.ZERO) > 0;
}
private void handleSameDaySaleAndBuyback(final LocalDate settlementDate, final List<ExternalAssetOwnerTransfer> transferDataList,
@@ -32,6 +32,7 @@ public class ExternalTransferData {
private String purchasePriceRatio;
private LocalDate settlementDate;
private ExternalTransferStatus status;
private ExternalTransferSubStatus subStatus;
private LocalDate effectiveFrom;
private LocalDate effectiveTo;
}
@@ -49,4 +49,6 @@ public interface ExternalAssetOwnerTransferRepository
@Query("SELECT t FROM ExternalAssetOwnerTransfer t WHERE t.loanId = :loanId AND t.effectiveDateTo > :effectiveDate")
List<ExternalAssetOwnerTransfer> findEffectiveTransfers(@Param("loanId") Long loanId, @Param("effectiveDate") LocalDate effectiveDate);
Optional<ExternalAssetOwnerTransfer> findFirstByExternalIdOrderByIdAsc(ExternalId externalTransferId);
}
@@ -31,6 +31,12 @@ public class ExternalAssetOwnerTransferNotFoundException extends AbstractPlatfor
externalId.getValue(), externalTransferStatus);
}
public ExternalAssetOwnerTransferNotFoundException(ExternalId externalId) {
super("error.msg.external.asset.owner.transfer.external.id",
String.format("External asset owner transfer with external id: %s does not found", externalId.getValue()),
externalId.getValue());
}
public ExternalAssetOwnerTransferNotFoundException(Long id) {
super("error.msg.external.asset.owner.transfer.id", String.format("External asset owner transfer with id: %s does not found", id),
id);
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.investor.service;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.investor.data.ExternalOwnerJournalEntryData;
import org.apache.fineract.investor.data.ExternalOwnerTransferJournalEntryData;
import org.apache.fineract.investor.data.ExternalTransferData;
@@ -25,14 +26,16 @@ import org.springframework.data.domain.Page;
public interface ExternalAssetOwnersReadService {
Page<ExternalTransferData> retrieveTransferData(Long loanId, String externalLoanId, String transferExternalId, Integer offset,
Page<ExternalTransferData> retrieveTransferData(Long loanId, String externalLoanId, String externalTransferId, Integer offset,
Integer limit);
ExternalTransferData retrieveTransferData(Long transferExternalId);
ExternalTransferData retrieveTransferData(Long transferId);
ExternalTransferData retrieveActiveTransferData(Long loanId, String externalLoanId, String transferExternalId);
ExternalOwnerTransferJournalEntryData retrieveJournalEntriesOfTransfer(Long transferId, Integer offset, Integer limit);
ExternalOwnerJournalEntryData retrieveJournalEntriesOfOwner(String ownerExternalId, Integer offset, Integer limit);
ExternalTransferData retrieveFirstTransferByExternalId(ExternalId externalTransferId);
}
@@ -21,6 +21,7 @@ package org.apache.fineract.investor.service;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.journalentry.JournalEntryMapper;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.investor.data.ExternalOwnerJournalEntryData;
import org.apache.fineract.investor.data.ExternalOwnerTransferJournalEntryData;
@@ -53,7 +54,7 @@ public class ExternalAssetOwnersReadServiceImpl implements ExternalAssetOwnersRe
private final JournalEntryMapper journalEntryMapper;
@Override
public Page<ExternalTransferData> retrieveTransferData(Long loanId, String externalLoanId, String transferExternalId, Integer offset,
public Page<ExternalTransferData> retrieveTransferData(Long loanId, String externalLoanId, String externalTransferId, Integer offset,
Integer limit) {
Page<ExternalAssetOwnerTransfer> result;
PageRequest pageRequest = getPageRequest(offset, limit);
@@ -61,8 +62,8 @@ public class ExternalAssetOwnersReadServiceImpl implements ExternalAssetOwnersRe
result = externalAssetOwnerTransferRepository.findAllByLoanId(loanId, pageRequest);
} else if (externalLoanId != null) {
result = externalAssetOwnerTransferRepository.findAllByExternalLoanId(ExternalIdFactory.produce(externalLoanId), pageRequest);
} else if (transferExternalId != null) {
result = externalAssetOwnerTransferRepository.findAllByExternalId(ExternalIdFactory.produce(transferExternalId), pageRequest);
} else if (externalTransferId != null) {
result = externalAssetOwnerTransferRepository.findAllByExternalId(ExternalIdFactory.produce(externalTransferId), pageRequest);
} else {
throw new IllegalArgumentException(
"At least one of the following parameters must be provided: loanId, externalLoanId, transferExternalId");
@@ -113,6 +114,18 @@ public class ExternalAssetOwnersReadServiceImpl implements ExternalAssetOwnersRe
return mappedResult;
}
@Override
public ExternalTransferData retrieveFirstTransferByExternalId(ExternalId externalTransferId) {
return externalAssetOwnerTransferRepository.findFirstByExternalIdOrderByIdAsc(externalTransferId).map(mapper::mapTransfer)
.orElseThrow(() -> new ExternalAssetOwnerTransferNotFoundException(externalTransferId));
}
@Override
public ExternalTransferData retrieveTransferData(Long transferId) {
return externalAssetOwnerTransferRepository.findById(transferId).map(mapper::mapTransfer)
.orElseThrow(() -> new ExternalAssetOwnerTransferNotFoundException(transferId));
}
private PageRequest getPageRequest(Integer offset, Integer limit) {
if (offset == null) {
offset = 0;
@@ -123,10 +136,4 @@ public class ExternalAssetOwnersReadServiceImpl implements ExternalAssetOwnersRe
return PageRequest.of(offset, limit, Sort.by("id"));
}
@Override
public ExternalTransferData retrieveTransferData(Long externalTransferId) {
return externalAssetOwnerTransferRepository.findById(externalTransferId).map(mapper::mapTransfer)
.orElseThrow(() -> new ExternalAssetOwnerTransferNotFoundException(externalTransferId));
}
}
@@ -23,15 +23,11 @@ import static org.apache.fineract.investor.data.ExternalTransferStatus.ACTIVE;
import static org.apache.fineract.investor.data.ExternalTransferStatus.BUYBACK;
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.ExternalTransferSubStatus.BALANCE_NEGATIVE;
import static org.apache.fineract.investor.data.ExternalTransferSubStatus.BALANCE_ZERO;
import static org.apache.fineract.investor.data.ExternalTransferSubStatus.SAMEDAY_TRANSFERS;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.avro.generic.GenericContainer;
import org.apache.fineract.avro.generator.ByteBufferSerializable;
@@ -42,11 +38,12 @@ import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
import org.apache.fineract.investor.data.ExternalTransferData;
import org.apache.fineract.investor.data.ExternalTransferStatus;
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
import org.apache.fineract.investor.data.ExternalTransferSubStatus;
import org.apache.fineract.investor.domain.InvestorBusinessEvent;
import org.apache.fineract.investor.service.ExternalAssetOwnersReadService;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
@Component
@@ -76,15 +73,21 @@ public class InvestorBusinessEventSerializer implements BusinessEventSerializer
public <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
InvestorBusinessEvent event = (InvestorBusinessEvent) rawEvent;
ExternalTransferData transferData = externalAssetOwnersReadService.retrieveTransferData(event.get().getId());
String transferType = getType(transferData.getStatus());
if (ExternalTransferStatus.DECLINED.equals(transferData.getStatus()) || CANCELLED.equals(transferData.getStatus())) {
ExternalTransferData originalTransferData = externalAssetOwnersReadService
.retrieveFirstTransferByExternalId(event.get().getExternalId());
transferType = getType(originalTransferData.getStatus());
}
LoanOwnershipTransferDataV1.Builder builder = LoanOwnershipTransferDataV1.newBuilder().setLoanId(transferData.getLoan().getLoanId())
.setLoanExternalId(transferData.getLoan().getExternalId()).setTransferExternalId(transferData.getTransferExternalId())
.setAssetOwnerExternalId(transferData.getOwner().getExternalId())
.setPurchasePriceRatio(transferData.getPurchasePriceRatio()).setCurrency(getCurrencyFromEvent(event))
.setSettlementDate(transferData.getSettlementDate().format(DEFAULT_DATE_FORMATTER))
.setSubmittedDate(transferData.getSettlementDate().format(DEFAULT_DATE_FORMATTER))
.setType(transferData.getStatus() == BUYBACK ? "BUYBACK" : "SALE").setTransferStatus(getStatus(event.get()))
.setTransferStatusReason(getTransferStatusReason(event.get()).orElse(null));
.setSubmittedDate(transferData.getSettlementDate().format(DEFAULT_DATE_FORMATTER)).setType(transferType)
.setTransferStatus(getStatus(transferData.getStatus()))
.setTransferStatusReason(getTransferStatusReason(transferData.getSubStatus()));
if (transferData.getDetails() != null) {
builder.setTotalOutstandingBalanceAmount(transferData.getDetails().getTotalOutstanding())
@@ -98,6 +101,11 @@ public class InvestorBusinessEventSerializer implements BusinessEventSerializer
return builder.build();
}
@NotNull
private static String getType(ExternalTransferStatus transferStatus) {
return transferStatus == BUYBACK ? "BUYBACK" : "SALE";
}
private List<UnpaidChargeDataV1> getUnpaidChargeData(InvestorBusinessEvent event) {
java.util.Map<Long, UnpaidChargeDataV1> map = new HashMap<>();
event.getLoan().getLoanCharges().forEach(loanCharge -> addToMap(map, loanCharge));
@@ -117,29 +125,21 @@ public class InvestorBusinessEventSerializer implements BusinessEventSerializer
}
}
private String getStatus(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
ExternalTransferStatus status = externalAssetOwnerTransfer.getStatus();
private String getStatus(ExternalTransferStatus status) {
if (ACTIVE.equals(status) || BUYBACK.equals(status)) {
return "EXECUTED";
} else if (DECLINED.equals(status)) {
return "DECLINED";
} else if (CANCELLED.equals(status)) {
return "CANCELLED";
} else if (DECLINED.equals(status) || CANCELLED.equals(status)) {
return status.name();
} else {
return "UNKNOWN";
}
}
private Optional<String> getTransferStatusReason(ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
ExternalTransferStatus status = externalAssetOwnerTransfer.getStatus();
if (DECLINED.equals(status) && BALANCE_ZERO.equals(externalAssetOwnerTransfer.getSubStatus())) {
return Optional.of("BALANCE ZERO");
} else if (DECLINED.equals(status) && BALANCE_NEGATIVE.equals(externalAssetOwnerTransfer.getSubStatus())) {
return Optional.of("BALANCE NEGATIV");
} else if (CANCELLED.equals(status) && SAMEDAY_TRANSFERS.equals(externalAssetOwnerTransfer.getSubStatus())) {
return Optional.of("SAMEDAY TRANSFER");
private String getTransferStatusReason(ExternalTransferSubStatus subStatus) {
if (subStatus != null) {
return subStatus.name();
} else {
return Optional.empty();
return null;
}
}
}