diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStep.java b/fineract-investor/src/main/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStep.java index 65a6b4edf..99b9c8ef8 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStep.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStep.java @@ -22,6 +22,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.Objects; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.cob.loan.LoanCOBBusinessStep; @@ -103,14 +104,19 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep private void handleBuyback(final Loan loan, final LocalDate settlementDate, final ExternalAssetOwnerTransfer buybackExternalAssetOwnerTransfer) { - ExternalAssetOwnerTransfer activeExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository + Optional optActiveExternalAssetOwnerTransfer = externalAssetOwnerTransferRepository .findOne((root, query, criteriaBuilder) -> criteriaBuilder.and(criteriaBuilder.equal(root.get("loanId"), loan.getId()), criteriaBuilder.equal(root.get("owner"), buybackExternalAssetOwnerTransfer.getOwner()), criteriaBuilder.equal(root.get("status"), ExternalTransferStatus.ACTIVE), - criteriaBuilder.equal(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31))) - .orElseThrow(); - ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer = buybackAsset(loan, settlementDate, buybackExternalAssetOwnerTransfer, - activeExternalAssetOwnerTransfer); + criteriaBuilder.equal(root.get("effectiveDateTo"), FUTURE_DATE_9999_12_31))); + ExternalAssetOwnerTransfer newExternalAssetOwnerTransfer; + if (!optActiveExternalAssetOwnerTransfer.isPresent()) { + newExternalAssetOwnerTransfer = createNewEntry(settlementDate, buybackExternalAssetOwnerTransfer, + ExternalTransferStatus.CANCELLED, ExternalTransferSubStatus.UNSOLD, settlementDate, settlementDate); + } else { + newExternalAssetOwnerTransfer = buybackAsset(loan, settlementDate, buybackExternalAssetOwnerTransfer, + optActiveExternalAssetOwnerTransfer.get()); + } businessEventNotifierService.notifyPostBusinessEvent(new LoanOwnershipTransferBusinessEvent(newExternalAssetOwnerTransfer, loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountSnapshotBusinessEvent(loan)); } diff --git a/fineract-investor/src/test/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStepTest.java b/fineract-investor/src/test/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStepTest.java index 8286eed6f..02643749b 100644 --- a/fineract-investor/src/test/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStepTest.java +++ b/fineract-investor/src/test/java/org/apache/fineract/investor/cob/loan/LoanAccountOwnerTransferBusinessStepTest.java @@ -383,6 +383,55 @@ public class LoanAccountOwnerTransferBusinessStepTest { assertEquals("Execute external asset owner transfer", actualEnumName); } + @Test + public void givenLoanSaleAnsBuyBackButBalanceIsNegative() { + // given + final Loan loanForProcessing = Mockito.mock(Loan.class); + when(loanForProcessing.getId()).thenReturn(1L); + LoanSummary loanSummary = Mockito.mock(LoanSummary.class); + when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary); + when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ZERO); + when(loanForProcessing.getTotalOverpaid()).thenReturn(BigDecimal.ONE); + ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class); + ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class); + secondResponseItem.setSettlementDate(actualDate.plusDays(2)); + + ExternalAssetOwnerTransfer firstSaveResult = Mockito.mock(ExternalAssetOwnerTransfer.class); + ExternalAssetOwnerTransfer secondSaveResult = Mockito.mock(ExternalAssetOwnerTransfer.class); + secondSaveResult.setSettlementDate(actualDate.plusDays(2)); + ExternalAssetOwnerTransfer thirdSaveResult = Mockito.mock(ExternalAssetOwnerTransfer.class); + ExternalAssetOwnerTransfer fourthSaveResult = Mockito.mock(ExternalAssetOwnerTransfer.class); + + when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.PENDING); + when(externalAssetOwnerTransferRepository.save(any(ExternalAssetOwnerTransfer.class))).thenReturn(firstSaveResult) + .thenReturn(secondSaveResult).thenReturn(thirdSaveResult).thenReturn(fourthSaveResult); + List response = List.of(firstResponseItem); + when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")))) + .thenReturn(response); + ArgumentCaptor externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor + .forClass(ExternalAssetOwnerTransfer.class); + // when + Loan processedLoan = underTest.execute(loanForProcessing); + // then + verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))); + verify(firstResponseItem).setEffectiveDateTo(actualDate); + verify(externalAssetOwnerTransferRepository, times(2)).save(externalAssetOwnerTransferArgumentCaptor.capture()); + + assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getOwner(), + externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getOwner()); + assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getExternalId(), + externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getExternalId()); + assertEquals(ExternalTransferStatus.DECLINED, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getStatus()); + assertEquals(ExternalTransferSubStatus.BALANCE_NEGATIVE, + externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getSubStatus()); + assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getSettlementDate()); + + assertEquals(processedLoan, loanForProcessing); + + ArgumentCaptor> businessEventArgumentCaptor = verifyBusinessEvents(2); + verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, secondSaveResult); + } + @NotNull private ArgumentCaptor> verifyBusinessEvents(int expectedBusinessEvents) { @SuppressWarnings("unchecked")