FINERACT-2141: Delayed settlement implementation for fineract loan investor module
This commit is contained in:
committed by
Arnold Gálovics
parent
23887bdf1c
commit
33e9f7f3db
+293
-166
@@ -24,18 +24,20 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
@@ -45,19 +47,26 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAccount
|
||||
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.ExternalAssetOwner;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMapping;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
|
||||
import org.apache.fineract.investor.domain.LoanOwnershipTransferBusinessEvent;
|
||||
import org.apache.fineract.investor.service.AccountingService;
|
||||
import org.apache.fineract.investor.service.DelayedSettlementAttributeService;
|
||||
import org.apache.fineract.investor.service.LoanTransferabilityService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
@@ -69,17 +78,27 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
|
||||
public static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||
private static final Long LOAN_PRODUCT_ID = 2L;
|
||||
private final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault());
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
|
||||
|
||||
@Mock
|
||||
private AccountingService accountingService;
|
||||
|
||||
@Mock
|
||||
private BusinessEventNotifierService businessEventNotifierService;
|
||||
|
||||
@Mock
|
||||
private AccountingService accountingService;
|
||||
private LoanTransferabilityService loanTransferabilityService;
|
||||
|
||||
@Mock
|
||||
private DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||
|
||||
private LoanAccountOwnerTransferBusinessStep underTest;
|
||||
|
||||
@BeforeEach
|
||||
@@ -88,7 +107,8 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
|
||||
underTest = new LoanAccountOwnerTransferBusinessStep(externalAssetOwnerTransferRepository,
|
||||
externalAssetOwnerTransferLoanMappingRepository, accountingService, businessEventNotifierService);
|
||||
externalAssetOwnerTransferLoanMappingRepository, accountingService, businessEventNotifierService,
|
||||
loanTransferabilityService, delayedSettlementAttributeService);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -106,15 +126,21 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
// then
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
verifyNoInteractions(businessEventNotifierService);
|
||||
verifyNoInteractions(businessEventNotifierService, loanTransferabilityService, accountingService);
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanTwoTransferButInvalidTransfers() {
|
||||
// given
|
||||
final LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.getId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(delayedSettlementAttributeService.isEnabled(LOAN_PRODUCT_ID)).thenReturn(false);
|
||||
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.PENDING);
|
||||
@@ -127,14 +153,46 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
// then
|
||||
assertEquals("Illegal transfer found. Expected PENDING and BUYBACK, found: PENDING and ACTIVE", exception.getMessage());
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
verifyNoInteractions(businessEventNotifierService);
|
||||
verifyNoInteractions(businessEventNotifierService, loanTransferabilityService, accountingService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenSameDaySaleAndBuybackWithDelayedSettlement() {
|
||||
// given
|
||||
final LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.getId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(delayedSettlementAttributeService.isEnabled(LOAN_PRODUCT_ID)).thenReturn(true);
|
||||
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.PENDING);
|
||||
when(secondResponseItem.getStatus()).thenReturn(ExternalTransferStatus.BUYBACK);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem, secondResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
// when
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> underTest.execute(loanForProcessing));
|
||||
// then
|
||||
assertEquals("Delayed Settlement enabled, but found 2 transfers of statuses: PENDING and BUYBACK", exception.getMessage());
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
verifyNoInteractions(businessEventNotifierService, loanTransferabilityService, accountingService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanTwoTransferSameDay() {
|
||||
// given
|
||||
final LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.getId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(delayedSettlementAttributeService.isEnabled(LOAN_PRODUCT_ID)).thenReturn(false);
|
||||
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
@@ -192,13 +250,20 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
|
||||
verifyNoInteractions(loanTransferabilityService, accountingService);
|
||||
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, secondSaveResult);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 1, loanForProcessing, fourthSaveResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanBuyback() {
|
||||
private static Stream<Arguments> buybackStatusDataProvider() {
|
||||
return Stream.of(Arguments.of(ExternalTransferStatus.BUYBACK_INTERMEDIATE), Arguments.of(ExternalTransferStatus.BUYBACK));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("buybackStatusDataProvider")
|
||||
public void givenLoanBuyback(final ExternalTransferStatus buybackStatus) {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
@@ -206,7 +271,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.BUYBACK);
|
||||
when(firstResponseItem.getStatus()).thenReturn(buybackStatus);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
@@ -218,6 +283,8 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
// when
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
// then
|
||||
verifyNoInteractions(loanTransferabilityService);
|
||||
|
||||
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());
|
||||
@@ -226,147 +293,248 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
|
||||
verify(accountingService).createJournalEntriesForBuybackAssetTransfer(loanForProcessing, firstResponseItem);
|
||||
verifyNoMoreInteractions(accountingService);
|
||||
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, firstResponseItem);
|
||||
verifyLoanAccountSnapshotBusinessEvent(businessEventArgumentCaptor, 1, loanForProcessing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanSale() {
|
||||
private static Stream<Arguments> loanSaleTransferableDataProvider() {
|
||||
return Stream.of(Arguments.of(false, ExternalTransferStatus.PENDING, ExternalTransferStatus.ACTIVE),
|
||||
Arguments.of(true, ExternalTransferStatus.PENDING_INTERMEDIATE, ExternalTransferStatus.ACTIVE_INTERMEDIATE));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("loanSaleTransferableDataProvider")
|
||||
public void givenLoanSaleTransferable(final boolean isDelayedSettlementEnabled, final ExternalTransferStatus pendingStatus,
|
||||
final ExternalTransferStatus expectedActiveStatus) {
|
||||
// given
|
||||
final LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.getId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.PENDING);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferLoanMapping> externalAssetOwnerTransferLoanMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferLoanMapping.class);
|
||||
ExternalAssetOwnerTransfer newTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(firstResponseItem).thenReturn(newTransfer);
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(delayedSettlementAttributeService.isEnabled(LOAN_PRODUCT_ID)).thenReturn(isDelayedSettlementEnabled);
|
||||
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(newTransfer.getStatus()).thenReturn(ExternalTransferStatus.ACTIVE);
|
||||
|
||||
ExternalAssetOwnerTransfer pendingTransfer = new ExternalAssetOwnerTransfer();
|
||||
pendingTransfer.setStatus(pendingStatus);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(pendingTransfer);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
|
||||
when(loanTransferabilityService.isTransferable(loanForProcessing, pendingTransfer)).thenReturn(true);
|
||||
|
||||
ExternalAssetOwnerTransfer savedNewTransfer = new ExternalAssetOwnerTransfer();
|
||||
savedNewTransfer.setStatus(expectedActiveStatus);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(pendingTransfer).thenReturn(savedNewTransfer);
|
||||
|
||||
// when
|
||||
final 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());
|
||||
verify(loanTransferabilityService).isTransferable(loanForProcessing, pendingTransfer);
|
||||
verifyNoMoreInteractions(loanTransferabilityService);
|
||||
verify(externalAssetOwnerTransferRepository).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getOwner(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getOwner());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getExternalId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getExternalId());
|
||||
assertEquals(ExternalTransferStatus.ACTIVE, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getStatus());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getSettlementDate());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getLoanId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getLoanId());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getPurchasePriceRatio(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getPurchasePriceRatio());
|
||||
assertEquals(actualDate.plusDays(1), externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateFrom());
|
||||
assertEquals(FUTURE_DATE_9999_12_31, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateTo());
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
verify(externalAssetOwnerTransferRepository, times(2)).save(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
ExternalAssetOwnerTransfer capturedPendingTransfer = externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0);
|
||||
ExternalAssetOwnerTransfer capturedActiveTransfer = externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1);
|
||||
|
||||
assertEquals(actualDate, capturedPendingTransfer.getEffectiveDateTo());
|
||||
|
||||
assertCommonFieldsOfPendingAndActiveTransfers(capturedPendingTransfer, capturedActiveTransfer);
|
||||
assertEquals(expectedActiveStatus, capturedActiveTransfer.getStatus());
|
||||
assertEquals(actualDate, capturedActiveTransfer.getSettlementDate());
|
||||
assertEquals(actualDate.plusDays(1), capturedActiveTransfer.getEffectiveDateFrom());
|
||||
assertEquals(FUTURE_DATE_9999_12_31, capturedActiveTransfer.getEffectiveDateTo());
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferLoanMapping> externalAssetOwnerTransferLoanMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferLoanMapping.class);
|
||||
verify(externalAssetOwnerTransferLoanMappingRepository).save(externalAssetOwnerTransferLoanMappingArgumentCaptor.capture());
|
||||
assertEquals(1L, externalAssetOwnerTransferLoanMappingArgumentCaptor.getValue().getLoanId());
|
||||
assertEquals(savedNewTransfer, externalAssetOwnerTransferLoanMappingArgumentCaptor.getValue().getOwnerTransfer());
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
|
||||
verify(externalAssetOwnerTransferLoanMappingRepository).save(externalAssetOwnerTransferLoanMappingArgumentCaptor.capture());
|
||||
|
||||
verify(accountingService).createJournalEntriesForSaleAssetTransfer(loanForProcessing, savedNewTransfer, null);
|
||||
verifyNoMoreInteractions(accountingService);
|
||||
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, savedNewTransfer);
|
||||
verifyLoanAccountSnapshotBusinessEvent(businessEventArgumentCaptor, 1, loanForProcessing);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> loanSaleNotTransferableDataProvider() {
|
||||
return Stream.of(Arguments.of(ExternalTransferStatus.PENDING, ExternalTransferSubStatus.BALANCE_ZERO),
|
||||
Arguments.of(ExternalTransferStatus.PENDING, ExternalTransferSubStatus.BALANCE_NEGATIVE),
|
||||
Arguments.of(ExternalTransferStatus.PENDING_INTERMEDIATE, ExternalTransferSubStatus.BALANCE_ZERO),
|
||||
Arguments.of(ExternalTransferStatus.PENDING_INTERMEDIATE, ExternalTransferSubStatus.BALANCE_NEGATIVE));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("loanSaleNotTransferableDataProvider")
|
||||
public void givenLoanSaleNotTransferable(final ExternalTransferStatus pendingStatus,
|
||||
final ExternalTransferSubStatus expectedSubStatus) {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
|
||||
ExternalAssetOwnerTransfer pendingTransfer = new ExternalAssetOwnerTransfer();
|
||||
pendingTransfer.setStatus(pendingStatus);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(pendingTransfer);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
|
||||
when(loanTransferabilityService.isTransferable(loanForProcessing, pendingTransfer)).thenReturn(false);
|
||||
when(loanTransferabilityService.getDeclinedSubStatus(loanForProcessing)).thenReturn(expectedSubStatus);
|
||||
|
||||
ExternalAssetOwnerTransfer savedNewTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(savedNewTransfer.getStatus()).thenReturn(ExternalTransferStatus.DECLINED);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(pendingTransfer).thenReturn(savedNewTransfer);
|
||||
|
||||
// when
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
// then
|
||||
verify(loanTransferabilityService).isTransferable(loanForProcessing, pendingTransfer);
|
||||
verify(externalAssetOwnerTransferRepository, times(1)).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
verify(externalAssetOwnerTransferRepository, times(2)).save(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
ExternalAssetOwnerTransfer capturedPendingTransfer = externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0);
|
||||
ExternalAssetOwnerTransfer capturedActiveTransfer = externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1);
|
||||
|
||||
assertEquals(actualDate, capturedPendingTransfer.getEffectiveDateTo());
|
||||
|
||||
assertCommonFieldsOfPendingAndActiveTransfers(capturedPendingTransfer, capturedActiveTransfer);
|
||||
assertEquals(ExternalTransferStatus.DECLINED, capturedActiveTransfer.getStatus());
|
||||
assertEquals(expectedSubStatus, capturedActiveTransfer.getSubStatus());
|
||||
assertEquals(actualDate, capturedActiveTransfer.getSettlementDate());
|
||||
assertEquals(actualDate, capturedActiveTransfer.getEffectiveDateFrom());
|
||||
assertEquals(actualDate, capturedActiveTransfer.getEffectiveDateTo());
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
|
||||
verifyNoInteractions(accountingService);
|
||||
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(1);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, savedNewTransfer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaleLoanWithDelayedSettlementFromIntermediateToInvestor() {
|
||||
// given
|
||||
final LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.getId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(delayedSettlementAttributeService.isEnabled(LOAN_PRODUCT_ID)).thenReturn(true);
|
||||
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
|
||||
ExternalAssetOwner previousOwner = new ExternalAssetOwner();
|
||||
ExternalAssetOwnerTransfer activeIntermediateTransfer = new ExternalAssetOwnerTransfer();
|
||||
activeIntermediateTransfer.setOwner(previousOwner);
|
||||
activeIntermediateTransfer.setStatus(ExternalTransferStatus.ACTIVE_INTERMEDIATE);
|
||||
when(externalAssetOwnerTransferRepository.findOne(any(Specification.class))).thenReturn(Optional.of(activeIntermediateTransfer));
|
||||
|
||||
ExternalAssetOwnerTransfer pendingTransfer = new ExternalAssetOwnerTransfer();
|
||||
pendingTransfer.setStatus(ExternalTransferStatus.PENDING);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(pendingTransfer);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
|
||||
when(loanTransferabilityService.isTransferable(loanForProcessing, pendingTransfer)).thenReturn(true);
|
||||
|
||||
ExternalAssetOwnerTransfer savedNewTransfer = new ExternalAssetOwnerTransfer();
|
||||
savedNewTransfer.setStatus(ExternalTransferStatus.ACTIVE);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(pendingTransfer).thenReturn(savedNewTransfer);
|
||||
|
||||
// when
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
|
||||
// then
|
||||
verify(loanTransferabilityService).isTransferable(loanForProcessing, pendingTransfer);
|
||||
verifyNoMoreInteractions(loanTransferabilityService);
|
||||
verify(externalAssetOwnerTransferRepository).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
verify(externalAssetOwnerTransferRepository).findOne(any(Specification.class));
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
verify(externalAssetOwnerTransferRepository, times(3)).save(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
ExternalAssetOwnerTransfer capturedActiveIntermediateTransfer = externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0);
|
||||
ExternalAssetOwnerTransfer capturedPendingTransfer = externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1);
|
||||
ExternalAssetOwnerTransfer capturedActiveTransfer = externalAssetOwnerTransferArgumentCaptor.getAllValues().get(2);
|
||||
|
||||
assertEquals(actualDate, capturedActiveIntermediateTransfer.getEffectiveDateTo());
|
||||
assertEquals(actualDate, capturedPendingTransfer.getEffectiveDateTo());
|
||||
|
||||
assertCommonFieldsOfPendingAndActiveTransfers(capturedPendingTransfer, capturedActiveTransfer);
|
||||
assertEquals(ExternalTransferStatus.ACTIVE, capturedActiveTransfer.getStatus());
|
||||
assertEquals(actualDate, capturedActiveTransfer.getSettlementDate());
|
||||
assertEquals(actualDate.plusDays(1), capturedActiveTransfer.getEffectiveDateFrom());
|
||||
assertEquals(FUTURE_DATE_9999_12_31, capturedActiveTransfer.getEffectiveDateTo());
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferLoanMapping> externalAssetOwnerTransferLoanMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferLoanMapping.class);
|
||||
verify(externalAssetOwnerTransferLoanMappingRepository, times(1))
|
||||
.save(externalAssetOwnerTransferLoanMappingArgumentCaptor.capture());
|
||||
assertEquals(1L, externalAssetOwnerTransferLoanMappingArgumentCaptor.getValue().getLoanId());
|
||||
assertEquals(newTransfer, externalAssetOwnerTransferLoanMappingArgumentCaptor.getValue().getOwnerTransfer());
|
||||
assertEquals(savedNewTransfer, externalAssetOwnerTransferLoanMappingArgumentCaptor.getValue().getOwnerTransfer());
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
|
||||
verify(accountingService).createJournalEntriesForSaleAssetTransfer(loanForProcessing, savedNewTransfer, previousOwner);
|
||||
verifyNoMoreInteractions(accountingService);
|
||||
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, newTransfer);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, savedNewTransfer);
|
||||
verifyLoanAccountSnapshotBusinessEvent(businessEventArgumentCaptor, 1, loanForProcessing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanSaleButBalanceIsZero() {
|
||||
public void testSaleLoanWithDelayedSettlementFromIntermediateToInvestorActiveIntermediateTransferNotFound() {
|
||||
// given
|
||||
final LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.getId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.PENDING);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem);
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(delayedSettlementAttributeService.isEnabled(LOAN_PRODUCT_ID)).thenReturn(true);
|
||||
|
||||
when(externalAssetOwnerTransferRepository.findOne(any(Specification.class))).thenReturn(Optional.empty());
|
||||
|
||||
ExternalAssetOwnerTransfer pendingTransfer = new ExternalAssetOwnerTransfer();
|
||||
pendingTransfer.setStatus(ExternalTransferStatus.PENDING);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(pendingTransfer);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer newTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(firstResponseItem).thenReturn(newTransfer);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ZERO);
|
||||
when(loanForProcessing.getTotalOverpaid()).thenReturn(BigDecimal.ZERO);
|
||||
when(newTransfer.getStatus()).thenReturn(ExternalTransferStatus.DECLINED);
|
||||
when(loanTransferabilityService.isTransferable(loanForProcessing, pendingTransfer)).thenReturn(true);
|
||||
|
||||
// when
|
||||
final Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> 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("Expected a effective transfer of ACTIVE_INTERMEDIATE status to be present.", exception.getMessage());
|
||||
|
||||
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(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getSettlementDate());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getLoanId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getLoanId());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getPurchasePriceRatio(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getPurchasePriceRatio());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateFrom());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateTo());
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(1);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, newTransfer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLoanSaleButBalanceIsNegative() {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.PENDING);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer newTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(firstResponseItem).thenReturn(newTransfer);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ONE.negate());
|
||||
when(loanForProcessing.getTotalOverpaid()).thenReturn(BigDecimal.ONE.negate());
|
||||
when(newTransfer.getStatus()).thenReturn(ExternalTransferStatus.DECLINED);
|
||||
// when
|
||||
final 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(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getSettlementDate());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getLoanId(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getLoanId());
|
||||
assertEquals(externalAssetOwnerTransferArgumentCaptor.getAllValues().get(0).getPurchasePriceRatio(),
|
||||
externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getPurchasePriceRatio());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateFrom());
|
||||
assertEquals(actualDate, externalAssetOwnerTransferArgumentCaptor.getAllValues().get(1).getEffectiveDateTo());
|
||||
assertEquals(processedLoan, loanForProcessing);
|
||||
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(1);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, newTransfer);
|
||||
verify(loanTransferabilityService).isTransferable(loanForProcessing, pendingTransfer);
|
||||
verifyNoMoreInteractions(loanTransferabilityService);
|
||||
verifyNoInteractions(accountingService);
|
||||
verify(externalAssetOwnerTransferRepository).findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id")));
|
||||
verify(externalAssetOwnerTransferRepository).findOne(any(Specification.class));
|
||||
verify(externalAssetOwnerTransferRepository, never()).save(any(ExternalAssetOwnerTransfer.class));
|
||||
verifyNoInteractions(externalAssetOwnerTransferLoanMappingRepository);
|
||||
verifyBusinessEvents(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -383,55 +551,6 @@ 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.getSummary()).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<ExternalAssetOwnerTransfer> response = List.of(firstResponseItem);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(Sort.Direction.ASC, "id"))))
|
||||
.thenReturn(response);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> 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<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, secondSaveResult);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ArgumentCaptor<BusinessEvent<?>> verifyBusinessEvents(int expectedBusinessEvents) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -453,4 +572,12 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
assertTrue(businessEventArgumentCaptor.getAllValues().get(index) instanceof LoanAccountSnapshotBusinessEvent);
|
||||
assertEquals(expectedLoan, ((LoanAccountSnapshotBusinessEvent) businessEventArgumentCaptor.getAllValues().get(index)).get());
|
||||
}
|
||||
|
||||
private void assertCommonFieldsOfPendingAndActiveTransfers(final ExternalAssetOwnerTransfer pendingTransfer,
|
||||
final ExternalAssetOwnerTransfer activeTransfer) {
|
||||
assertEquals(pendingTransfer.getOwner(), activeTransfer.getOwner());
|
||||
assertEquals(pendingTransfer.getExternalId(), activeTransfer.getExternalId());
|
||||
assertEquals(pendingTransfer.getLoanId(), activeTransfer.getLoanId());
|
||||
assertEquals(pendingTransfer.getPurchasePriceRatio(), activeTransfer.getPurchasePriceRatio());
|
||||
}
|
||||
}
|
||||
|
||||
+594
@@ -0,0 +1,594 @@
|
||||
/**
|
||||
* 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.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.accounting.common.AccountingConstants;
|
||||
import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount;
|
||||
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.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
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;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMapping;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMappingRepository;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AccountingServiceImplTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BUSINESS_DATE, LocalDate.of(2024, 9, 27))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createJournalEntriesForSaleAssetTransfer() {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
Loan loan = testContext.createMockedLoan();
|
||||
|
||||
ExternalAssetOwner previousOwner = new ExternalAssetOwner();
|
||||
ExternalAssetOwner newOwner = new ExternalAssetOwner();
|
||||
ExternalAssetOwnerTransfer transfer = new ExternalAssetOwnerTransfer();
|
||||
transfer.setOwner(newOwner);
|
||||
|
||||
JournalEntry principleAndInterestDebitJournalEntry = createJournalEntry(11, testContext.principleAndInterestAccount,
|
||||
JournalEntryType.DEBIT);
|
||||
JournalEntry principleAndInterestCreditJournalEntry = createJournalEntry(12, testContext.principleAndInterestAccount,
|
||||
JournalEntryType.CREDIT);
|
||||
JournalEntry feeAndPenaltyDebitJournalEntry = createJournalEntry(13, testContext.feeAndPenaltyAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry feeAndPenaltyCreditJournalEntry = createJournalEntry(14, testContext.feeAndPenaltyAccount, JournalEntryType.CREDIT);
|
||||
JournalEntry transferDebitJournalEntry = createJournalEntry(15, testContext.transferAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry transferCreditJournalEntry = createJournalEntry(16, testContext.transferAccount, JournalEntryType.CREDIT);
|
||||
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(false), eq(testContext.principleAndInterestAccount))).thenReturn(principleAndInterestCreditJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(false), eq(testContext.feeAndPenaltyAccount))).thenReturn(feeAndPenaltyCreditJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(false))).thenReturn(transferDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(true), eq(testContext.principleAndInterestAccount))).thenReturn(principleAndInterestDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(true), eq(testContext.feeAndPenaltyAccount))).thenReturn(feeAndPenaltyDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(true))).thenReturn(transferCreditJournalEntry);
|
||||
|
||||
// when
|
||||
testContext.testSubject.createJournalEntriesForSaleAssetTransfer(loan, transfer, previousOwner);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<ExternalAssetOwnerJournalEntryMapping> ownerJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerJournalEntryMappingRepository, times(4))
|
||||
.saveAndFlush(ownerJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerJournalEntryMapping> capturedOwnerJournalEntryMappings = ownerJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerJournalEntryMapping> expectedOwnerJournalEntryMappings = List.of(
|
||||
ownerJournalEntryMapping(principleAndInterestCreditJournalEntry, previousOwner),
|
||||
ownerJournalEntryMapping(feeAndPenaltyCreditJournalEntry, previousOwner),
|
||||
ownerJournalEntryMapping(principleAndInterestDebitJournalEntry, newOwner),
|
||||
ownerJournalEntryMapping(feeAndPenaltyDebitJournalEntry, newOwner));
|
||||
assertNotNull(capturedOwnerJournalEntryMappings);
|
||||
assertOwnerJournalEntryMappings(expectedOwnerJournalEntryMappings, capturedOwnerJournalEntryMappings);
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferJournalEntryMapping> transferJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerTransferJournalEntryMappingRepository, times(6))
|
||||
.saveAndFlush(transferJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> capturedTransferJournalEntryMappings = transferJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> expectedTransferJournalEntryMappings = List.of(
|
||||
transferJournalEntryMapping(principleAndInterestCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(feeAndPenaltyCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(principleAndInterestDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(feeAndPenaltyDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferCreditJournalEntry, transfer));
|
||||
assertNotNull(capturedTransferJournalEntryMappings);
|
||||
assertTransferJournalEntryMappings(expectedTransferJournalEntryMappings, capturedTransferJournalEntryMappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createJournalEntriesForSaleAssetTransferOfOverpaidLoan() {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
Loan loan = testContext.createMockedOverpaidLoan();
|
||||
|
||||
ExternalAssetOwner previousOwner = new ExternalAssetOwner();
|
||||
ExternalAssetOwner newOwner = new ExternalAssetOwner();
|
||||
ExternalAssetOwnerTransfer transfer = new ExternalAssetOwnerTransfer();
|
||||
transfer.setOwner(newOwner);
|
||||
|
||||
JournalEntry overpaidDebitJournalEntry = createJournalEntry(11, testContext.overpaymentAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry overpaidCreditJournalEntry = createJournalEntry(12, testContext.overpaymentAccount, JournalEntryType.CREDIT);
|
||||
JournalEntry transferDebitJournalEntry = createJournalEntry(15, testContext.transferAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry transferCreditJournalEntry = createJournalEntry(16, testContext.transferAccount, JournalEntryType.CREDIT);
|
||||
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(false), eq(testContext.overpaymentAccount))).thenReturn(overpaidCreditJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(false))).thenReturn(transferDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(true), eq(testContext.overpaymentAccount))).thenReturn(overpaidDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(true))).thenReturn(transferCreditJournalEntry);
|
||||
|
||||
// when
|
||||
testContext.testSubject.createJournalEntriesForSaleAssetTransfer(loan, transfer, previousOwner);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<ExternalAssetOwnerJournalEntryMapping> ownerJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerJournalEntryMappingRepository, times(2))
|
||||
.saveAndFlush(ownerJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerJournalEntryMapping> capturedOwnerJournalEntryMappings = ownerJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerJournalEntryMapping> expectedOwnerJournalEntryMappings = List.of(
|
||||
ownerJournalEntryMapping(overpaidCreditJournalEntry, newOwner),
|
||||
ownerJournalEntryMapping(overpaidDebitJournalEntry, previousOwner));
|
||||
assertNotNull(capturedOwnerJournalEntryMappings);
|
||||
assertOwnerJournalEntryMappings(expectedOwnerJournalEntryMappings, capturedOwnerJournalEntryMappings);
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferJournalEntryMapping> transferJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerTransferJournalEntryMappingRepository, times(4))
|
||||
.saveAndFlush(transferJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> capturedTransferJournalEntryMappings = transferJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> expectedTransferJournalEntryMappings = List.of(
|
||||
transferJournalEntryMapping(overpaidCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(overpaidDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferCreditJournalEntry, transfer));
|
||||
assertNotNull(capturedTransferJournalEntryMappings);
|
||||
assertTransferJournalEntryMappings(expectedTransferJournalEntryMappings, capturedTransferJournalEntryMappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createJournalEntriesForSaleAssetTransferWithNullPreviousOwner() {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
Loan loan = testContext.createMockedLoan();
|
||||
|
||||
ExternalAssetOwner newOwner = new ExternalAssetOwner();
|
||||
ExternalAssetOwnerTransfer transfer = new ExternalAssetOwnerTransfer();
|
||||
transfer.setOwner(newOwner);
|
||||
|
||||
JournalEntry principleAndInterestDebitJournalEntry = createJournalEntry(11, testContext.principleAndInterestAccount,
|
||||
JournalEntryType.DEBIT);
|
||||
JournalEntry principleAndInterestCreditJournalEntry = createJournalEntry(12, testContext.principleAndInterestAccount,
|
||||
JournalEntryType.CREDIT);
|
||||
JournalEntry feeAndPenaltyDebitJournalEntry = createJournalEntry(13, testContext.feeAndPenaltyAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry feeAndPenaltyCreditJournalEntry = createJournalEntry(14, testContext.feeAndPenaltyAccount, JournalEntryType.CREDIT);
|
||||
JournalEntry transferDebitJournalEntry = createJournalEntry(15, testContext.transferAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry transferCreditJournalEntry = createJournalEntry(16, testContext.transferAccount, JournalEntryType.CREDIT);
|
||||
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(false), eq(testContext.principleAndInterestAccount))).thenReturn(principleAndInterestCreditJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(false), eq(testContext.feeAndPenaltyAccount))).thenReturn(feeAndPenaltyCreditJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(false))).thenReturn(transferDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(true), eq(testContext.principleAndInterestAccount))).thenReturn(principleAndInterestDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(true), eq(testContext.feeAndPenaltyAccount))).thenReturn(feeAndPenaltyDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(true))).thenReturn(transferCreditJournalEntry);
|
||||
|
||||
// when
|
||||
testContext.testSubject.createJournalEntriesForSaleAssetTransfer(loan, transfer, null);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<ExternalAssetOwnerJournalEntryMapping> ownerJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerJournalEntryMappingRepository, times(2))
|
||||
.saveAndFlush(ownerJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerJournalEntryMapping> capturedOwnerJournalEntryMappings = ownerJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerJournalEntryMapping> expectedOwnerJournalEntryMappings = List.of(
|
||||
ownerJournalEntryMapping(principleAndInterestDebitJournalEntry, newOwner),
|
||||
ownerJournalEntryMapping(feeAndPenaltyDebitJournalEntry, newOwner));
|
||||
assertNotNull(capturedOwnerJournalEntryMappings);
|
||||
assertOwnerJournalEntryMappings(expectedOwnerJournalEntryMappings, capturedOwnerJournalEntryMappings);
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferJournalEntryMapping> transferJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerTransferJournalEntryMappingRepository, times(6))
|
||||
.saveAndFlush(transferJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> capturedTransferJournalEntryMappings = transferJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> expectedTransferJournalEntryMappings = List.of(
|
||||
transferJournalEntryMapping(principleAndInterestCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(feeAndPenaltyCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(principleAndInterestDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(feeAndPenaltyDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferCreditJournalEntry, transfer));
|
||||
assertNotNull(capturedTransferJournalEntryMappings);
|
||||
assertTransferJournalEntryMappings(expectedTransferJournalEntryMappings, capturedTransferJournalEntryMappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createJournalEntriesForSaleAssetTransferOfOverpaidLoanWithNullPreviousOwner() {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
Loan loan = testContext.createMockedOverpaidLoan();
|
||||
|
||||
ExternalAssetOwner newOwner = new ExternalAssetOwner();
|
||||
ExternalAssetOwnerTransfer transfer = new ExternalAssetOwnerTransfer();
|
||||
transfer.setOwner(newOwner);
|
||||
|
||||
JournalEntry overpaidDebitJournalEntry = createJournalEntry(11, testContext.overpaymentAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry overpaidCreditJournalEntry = createJournalEntry(12, testContext.overpaymentAccount, JournalEntryType.CREDIT);
|
||||
JournalEntry transferDebitJournalEntry = createJournalEntry(15, testContext.transferAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry transferCreditJournalEntry = createJournalEntry(16, testContext.transferAccount, JournalEntryType.CREDIT);
|
||||
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(false), eq(testContext.overpaymentAccount))).thenReturn(overpaidCreditJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(false))).thenReturn(transferDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(true), eq(testContext.overpaymentAccount))).thenReturn(overpaidDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(true))).thenReturn(transferCreditJournalEntry);
|
||||
|
||||
// when
|
||||
testContext.testSubject.createJournalEntriesForSaleAssetTransfer(loan, transfer, null);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<ExternalAssetOwnerJournalEntryMapping> ownerJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerJournalEntryMappingRepository).saveAndFlush(ownerJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerJournalEntryMapping> capturedOwnerJournalEntryMappings = ownerJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerJournalEntryMapping> expectedOwnerJournalEntryMappings = List
|
||||
.of(ownerJournalEntryMapping(overpaidCreditJournalEntry, newOwner));
|
||||
assertNotNull(capturedOwnerJournalEntryMappings);
|
||||
assertOwnerJournalEntryMappings(expectedOwnerJournalEntryMappings, capturedOwnerJournalEntryMappings);
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferJournalEntryMapping> transferJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerTransferJournalEntryMappingRepository, times(4))
|
||||
.saveAndFlush(transferJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> capturedTransferJournalEntryMappings = transferJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> expectedTransferJournalEntryMappings = List.of(
|
||||
transferJournalEntryMapping(overpaidCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(overpaidDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferCreditJournalEntry, transfer));
|
||||
assertNotNull(capturedTransferJournalEntryMappings);
|
||||
assertTransferJournalEntryMappings(expectedTransferJournalEntryMappings, capturedTransferJournalEntryMappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createJournalEntriesForBuybackAssetTransfer() {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
Loan loan = testContext.createMockedLoan();
|
||||
|
||||
ExternalAssetOwner previousOwner = new ExternalAssetOwner();
|
||||
ExternalAssetOwnerTransfer transfer = new ExternalAssetOwnerTransfer();
|
||||
transfer.setOwner(previousOwner);
|
||||
|
||||
JournalEntry principleAndInterestDebitJournalEntry = createJournalEntry(11, testContext.principleAndInterestAccount,
|
||||
JournalEntryType.DEBIT);
|
||||
JournalEntry principleAndInterestCreditJournalEntry = createJournalEntry(12, testContext.principleAndInterestAccount,
|
||||
JournalEntryType.CREDIT);
|
||||
JournalEntry feeAndPenaltyDebitJournalEntry = createJournalEntry(13, testContext.feeAndPenaltyAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry feeAndPenaltyCreditJournalEntry = createJournalEntry(14, testContext.feeAndPenaltyAccount, JournalEntryType.CREDIT);
|
||||
JournalEntry transferDebitJournalEntry = createJournalEntry(15, testContext.transferAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry transferCreditJournalEntry = createJournalEntry(16, testContext.transferAccount, JournalEntryType.CREDIT);
|
||||
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(false), eq(testContext.principleAndInterestAccount))).thenReturn(principleAndInterestCreditJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(false), eq(testContext.feeAndPenaltyAccount))).thenReturn(feeAndPenaltyCreditJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(false))).thenReturn(transferDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(true), eq(testContext.principleAndInterestAccount))).thenReturn(principleAndInterestDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(true), eq(testContext.feeAndPenaltyAccount))).thenReturn(feeAndPenaltyDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(true))).thenReturn(transferCreditJournalEntry);
|
||||
|
||||
// when
|
||||
testContext.testSubject.createJournalEntriesForBuybackAssetTransfer(loan, transfer);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<ExternalAssetOwnerJournalEntryMapping> ownerJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerJournalEntryMappingRepository, times(2))
|
||||
.saveAndFlush(ownerJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerJournalEntryMapping> capturedOwnerJournalEntryMappings = ownerJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerJournalEntryMapping> expectedOwnerJournalEntryMappings = List.of(
|
||||
ownerJournalEntryMapping(principleAndInterestCreditJournalEntry, previousOwner),
|
||||
ownerJournalEntryMapping(feeAndPenaltyCreditJournalEntry, previousOwner));
|
||||
assertNotNull(capturedOwnerJournalEntryMappings);
|
||||
assertOwnerJournalEntryMappings(expectedOwnerJournalEntryMappings, capturedOwnerJournalEntryMappings);
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferJournalEntryMapping> transferJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerTransferJournalEntryMappingRepository, times(6))
|
||||
.saveAndFlush(transferJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> capturedTransferJournalEntryMappings = transferJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> expectedTransferJournalEntryMappings = List.of(
|
||||
transferJournalEntryMapping(principleAndInterestDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(feeAndPenaltyDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(principleAndInterestCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(feeAndPenaltyCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferDebitJournalEntry, transfer));
|
||||
assertNotNull(capturedTransferJournalEntryMappings);
|
||||
assertTransferJournalEntryMappings(expectedTransferJournalEntryMappings, capturedTransferJournalEntryMappings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createJournalEntriesForBuybackAssetTransferOfOverpaidLoan() {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
Loan loan = testContext.createMockedOverpaidLoan();
|
||||
|
||||
ExternalAssetOwner previousOwner = new ExternalAssetOwner();
|
||||
ExternalAssetOwnerTransfer transfer = new ExternalAssetOwnerTransfer();
|
||||
transfer.setOwner(previousOwner);
|
||||
|
||||
JournalEntry overpaidDebitJournalEntry = createJournalEntry(11, testContext.overpaymentAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry overpaidCreditJournalEntry = createJournalEntry(12, testContext.overpaymentAccount, JournalEntryType.CREDIT);
|
||||
JournalEntry transferDebitJournalEntry = createJournalEntry(15, testContext.transferAccount, JournalEntryType.DEBIT);
|
||||
JournalEntry transferCreditJournalEntry = createJournalEntry(16, testContext.transferAccount, JournalEntryType.CREDIT);
|
||||
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(false), eq(testContext.overpaymentAccount))).thenReturn(overpaidCreditJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(false))).thenReturn(transferDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createCreditJournalEntryOrReversalForInvestor(any(), any(), any(), any(), any(), any(),
|
||||
eq(true), eq(testContext.overpaymentAccount))).thenReturn(overpaidDebitJournalEntry);
|
||||
when(testContext.investorAccountingHelper.createDebitJournalEntryOrReversalForInvestor(any(), any(), anyInt(), any(), any(), any(),
|
||||
any(), any(), eq(true))).thenReturn(transferCreditJournalEntry);
|
||||
|
||||
// when
|
||||
testContext.testSubject.createJournalEntriesForBuybackAssetTransfer(loan, transfer);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<ExternalAssetOwnerJournalEntryMapping> ownerJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerJournalEntryMappingRepository, times(1))
|
||||
.saveAndFlush(ownerJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerJournalEntryMapping> capturedOwnerJournalEntryMappings = ownerJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerJournalEntryMapping> expectedOwnerJournalEntryMappings = List
|
||||
.of(ownerJournalEntryMapping(overpaidDebitJournalEntry, previousOwner));
|
||||
assertNotNull(capturedOwnerJournalEntryMappings);
|
||||
assertOwnerJournalEntryMappings(expectedOwnerJournalEntryMappings, capturedOwnerJournalEntryMappings);
|
||||
|
||||
ArgumentCaptor<ExternalAssetOwnerTransferJournalEntryMapping> transferJournalEntryMappingArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransferJournalEntryMapping.class);
|
||||
verify(testContext.externalAssetOwnerTransferJournalEntryMappingRepository, times(4))
|
||||
.saveAndFlush(transferJournalEntryMappingArgumentCaptor.capture());
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> capturedTransferJournalEntryMappings = transferJournalEntryMappingArgumentCaptor
|
||||
.getAllValues();
|
||||
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> expectedTransferJournalEntryMappings = List.of(
|
||||
transferJournalEntryMapping(overpaidDebitJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(overpaidCreditJournalEntry, transfer),
|
||||
transferJournalEntryMapping(transferDebitJournalEntry, transfer));
|
||||
assertNotNull(capturedTransferJournalEntryMappings);
|
||||
assertTransferJournalEntryMappings(expectedTransferJournalEntryMappings, capturedTransferJournalEntryMappings);
|
||||
}
|
||||
|
||||
private JournalEntry createJournalEntry(long id, GLAccount glAccount, JournalEntryType journalEntryType) {
|
||||
JournalEntry journalEntry = JournalEntry.createNew(null, null, glAccount, null, null, false, null, journalEntryType, null, null,
|
||||
null, null, null, null, null, null, null);
|
||||
journalEntry.setId(id);
|
||||
return journalEntry;
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerJournalEntryMapping ownerJournalEntryMapping(JournalEntry journalEntry, ExternalAssetOwner owner) {
|
||||
ExternalAssetOwnerJournalEntryMapping ownerJournalEntryMapping = new ExternalAssetOwnerJournalEntryMapping();
|
||||
ownerJournalEntryMapping.setJournalEntry(journalEntry);
|
||||
ownerJournalEntryMapping.setOwner(owner);
|
||||
return ownerJournalEntryMapping;
|
||||
}
|
||||
|
||||
private void assertOwnerJournalEntryMappings(List<ExternalAssetOwnerJournalEntryMapping> expectedOwnerJournalEntryMappings,
|
||||
List<ExternalAssetOwnerJournalEntryMapping> actualOwnerJournalEntryMappings) {
|
||||
assertEquals(expectedOwnerJournalEntryMappings.size(), actualOwnerJournalEntryMappings.size());
|
||||
for (int i = 0; i < expectedOwnerJournalEntryMappings.size(); i++) {
|
||||
assertOwnerJournalEntryMapping(expectedOwnerJournalEntryMappings.get(i), actualOwnerJournalEntryMappings.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertOwnerJournalEntryMapping(ExternalAssetOwnerJournalEntryMapping expectedOwnerJournalEntryMapping,
|
||||
ExternalAssetOwnerJournalEntryMapping actualOwnerJournalEntryMapping) {
|
||||
assertNotNull(actualOwnerJournalEntryMapping);
|
||||
assertSame(expectedOwnerJournalEntryMapping.getJournalEntry(), actualOwnerJournalEntryMapping.getJournalEntry());
|
||||
assertSame(expectedOwnerJournalEntryMapping.getOwner(), actualOwnerJournalEntryMapping.getOwner());
|
||||
}
|
||||
|
||||
private ExternalAssetOwnerTransferJournalEntryMapping transferJournalEntryMapping(JournalEntry journalEntry,
|
||||
ExternalAssetOwnerTransfer transfer) {
|
||||
ExternalAssetOwnerTransferJournalEntryMapping transferJournalEntryMapping = new ExternalAssetOwnerTransferJournalEntryMapping();
|
||||
transferJournalEntryMapping.setJournalEntry(journalEntry);
|
||||
transferJournalEntryMapping.setOwnerTransfer(transfer);
|
||||
return transferJournalEntryMapping;
|
||||
}
|
||||
|
||||
private void assertTransferJournalEntryMappings(
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> expectedTransferJournalEntryMappings,
|
||||
List<ExternalAssetOwnerTransferJournalEntryMapping> actualTransferJournalEntryMappings) {
|
||||
assertEquals(expectedTransferJournalEntryMappings.size(), actualTransferJournalEntryMappings.size());
|
||||
for (int i = 0; i < expectedTransferJournalEntryMappings.size(); i++) {
|
||||
assertTransferJournalEntryMapping(expectedTransferJournalEntryMappings.get(i), actualTransferJournalEntryMappings.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTransferJournalEntryMapping(ExternalAssetOwnerTransferJournalEntryMapping expectedTransferJournalEntryMapping,
|
||||
ExternalAssetOwnerTransferJournalEntryMapping actualTransferJournalEntryMapping) {
|
||||
assertNotNull(actualTransferJournalEntryMapping);
|
||||
assertSame(expectedTransferJournalEntryMapping.getJournalEntry(), actualTransferJournalEntryMapping.getJournalEntry());
|
||||
assertSame(expectedTransferJournalEntryMapping.getOwnerTransfer(), actualTransferJournalEntryMapping.getOwnerTransfer());
|
||||
}
|
||||
|
||||
private static class TestContext {
|
||||
|
||||
private static final Long LOAN_ID = 3001L;
|
||||
private static final Long LOAN_PRODUCT_ID = 3002L;
|
||||
private static final Long TRANSFER_ID = 3003L;
|
||||
|
||||
@Mock
|
||||
private InvestorAccountingHelper investorAccountingHelper;
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferJournalEntryMappingRepository externalAssetOwnerTransferJournalEntryMappingRepository;
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerJournalEntryMappingRepository externalAssetOwnerJournalEntryMappingRepository;
|
||||
|
||||
@Mock
|
||||
private FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository;
|
||||
|
||||
@InjectMocks
|
||||
private AccountingServiceImpl testSubject;
|
||||
|
||||
private final GLAccount principleAndInterestAccount = new GLAccount();
|
||||
private final GLAccount feeAndPenaltyAccount = new GLAccount();
|
||||
private final GLAccount transferAccount = new GLAccount();
|
||||
private final GLAccount overpaymentAccount = new GLAccount();
|
||||
|
||||
TestContext() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
setupAccounts();
|
||||
}
|
||||
|
||||
public Loan createMockedLoan() {
|
||||
Loan loan = mock(Loan.class);
|
||||
when(loan.getId()).thenReturn(LOAN_ID);
|
||||
when(loan.productId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
|
||||
Office office = Office.headOffice("office", LocalDate.of(2024, 9, 27), new ExternalId("officeId"));
|
||||
when(loan.getOffice()).thenReturn(office);
|
||||
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanSummary.getTotalPrincipalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanSummary.getTotalInterestOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanSummary.getTotalFeeChargesOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanSummary.getTotalPenaltyChargesOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loan.getSummary()).thenReturn(loanSummary);
|
||||
|
||||
return loan;
|
||||
}
|
||||
|
||||
public Loan createMockedOverpaidLoan() {
|
||||
Loan loan = mock(Loan.class);
|
||||
when(loan.getId()).thenReturn(LOAN_ID);
|
||||
when(loan.productId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
when(loan.getStatus()).thenReturn(LoanStatus.OVERPAID);
|
||||
|
||||
Office office = Office.headOffice("office", LocalDate.of(2024, 9, 27), new ExternalId("officeId"));
|
||||
when(loan.getOffice()).thenReturn(office);
|
||||
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loan.getSummary()).thenReturn(loanSummary);
|
||||
|
||||
when(loan.getTotalOverpaid()).thenReturn(BigDecimal.ONE);
|
||||
|
||||
return loan;
|
||||
}
|
||||
|
||||
private void setupAccounts() {
|
||||
principleAndInterestAccount.setId(1L);
|
||||
feeAndPenaltyAccount.setId(2L);
|
||||
transferAccount.setId(3L);
|
||||
overpaymentAccount.setId(4L);
|
||||
|
||||
FinancialActivityAccount financialActivityAccount = new FinancialActivityAccount(transferAccount,
|
||||
AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue());
|
||||
when(financialActivityAccountRepository
|
||||
.findByFinancialActivityTypeWithNotFoundDetection(AccountingConstants.FinancialActivity.ASSET_TRANSFER.getValue()))
|
||||
.thenReturn(financialActivityAccount);
|
||||
|
||||
lenient().when(investorAccountingHelper.getLinkedGLAccountForLoanProduct(LOAN_PRODUCT_ID,
|
||||
AccountingConstants.AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue())).thenReturn(principleAndInterestAccount);
|
||||
lenient()
|
||||
.when(investorAccountingHelper.getLinkedGLAccountForLoanProduct(LOAN_PRODUCT_ID,
|
||||
AccountingConstants.AccrualAccountsForLoan.INTEREST_RECEIVABLE.getValue()))
|
||||
.thenReturn(principleAndInterestAccount);
|
||||
lenient().when(investorAccountingHelper.getLinkedGLAccountForLoanProduct(LOAN_PRODUCT_ID,
|
||||
AccountingConstants.AccrualAccountsForLoan.FEES_RECEIVABLE.getValue())).thenReturn(feeAndPenaltyAccount);
|
||||
lenient().when(investorAccountingHelper.getLinkedGLAccountForLoanProduct(LOAN_PRODUCT_ID,
|
||||
AccountingConstants.AccrualAccountsForLoan.PENALTIES_RECEIVABLE.getValue())).thenReturn(feeAndPenaltyAccount);
|
||||
lenient().when(investorAccountingHelper.getLinkedGLAccountForLoanProduct(LOAN_PRODUCT_ID,
|
||||
AccountingConstants.AccrualAccountsForLoan.OVERPAYMENT.getValue())).thenReturn(overpaymentAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.DEFAULT_SETTLEMENT;
|
||||
import static org.apache.fineract.investor.data.attribute.SettlementModelExternalAssetOwnerLoanProductAttribute.DELAYED_SETTLEMENT;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.investor.data.ExternalTransferLoanProductAttributesData;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
public class DelayedSettlementAttributeServiceImplTest {
|
||||
|
||||
private static final Long LOAN_PRODUCT_ID = 1L;
|
||||
|
||||
private static Stream<Arguments> attributesDataProvider() {
|
||||
ExternalTransferLoanProductAttributesData enabledAttributesData = new ExternalTransferLoanProductAttributesData();
|
||||
enabledAttributesData.setAttributeValue(DELAYED_SETTLEMENT.getAttributeValue());
|
||||
|
||||
ExternalTransferLoanProductAttributesData disabledAttributesData = new ExternalTransferLoanProductAttributesData();
|
||||
disabledAttributesData.setAttributeValue(DEFAULT_SETTLEMENT.getAttributeValue());
|
||||
|
||||
return Stream.of(Arguments.of(new Page(List.of(enabledAttributesData), 1), true),
|
||||
Arguments.of(new Page(List.of(disabledAttributesData), 1), false), Arguments.of(new Page(List.of(), 0), false));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("attributesDataProvider")
|
||||
void isEnabled(final Page<ExternalTransferLoanProductAttributesData> attributesDataPage, final boolean expectedResult) {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
|
||||
when(testContext.externalAssetOwnerLoanProductAttributesReadService.retrieveAllLoanProductAttributesByLoanProductId(LOAN_PRODUCT_ID,
|
||||
DELAYED_SETTLEMENT.getAttributeKey())).thenReturn(attributesDataPage);
|
||||
|
||||
// when
|
||||
boolean result = testContext.testSubject.isEnabled(LOAN_PRODUCT_ID);
|
||||
|
||||
// then
|
||||
assertEquals(expectedResult, result);
|
||||
}
|
||||
|
||||
private static class TestContext {
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerLoanProductAttributesReadService externalAssetOwnerLoanProductAttributesReadService;
|
||||
|
||||
@InjectMocks
|
||||
private DelayedSettlementAttributeServiceImpl testSubject;
|
||||
|
||||
TestContext() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+932
@@ -0,0 +1,932 @@
|
||||
/**
|
||||
* 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.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.fineract.cob.data.LoanDataForExternalTransfer;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.investor.data.ExternalTransferRequestParameters;
|
||||
import org.apache.fineract.investor.data.ExternalTransferStatus;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwner;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerRepository;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
|
||||
import org.apache.fineract.investor.exception.ExternalAssetOwnerInitiateTransferException;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ExternalAssetOwnersWriteServiceTest {
|
||||
|
||||
final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault());
|
||||
private static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BUSINESS_DATE, actualDate)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntermediarySaleLoanByLoanIdHappyPath() {
|
||||
TestContext testContext = new TestContext();
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.externalAssetOwnerRepository.findByExternalId(any(ExternalId.class)))
|
||||
.thenReturn(Optional.of(testContext.externalAssetOwner));
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
|
||||
// when
|
||||
testContext.externalAssetOwnersWriteServiceImpl.intermediarySaleLoanByLoanId(command);
|
||||
|
||||
// then
|
||||
verify(testContext.externalAssetOwnerTransferRepository).saveAndFlush(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
verify(testContext.externalAssetOwnerRepository, times(0)).saveAndFlush(any(ExternalAssetOwner.class));
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
|
||||
assertAssertOwnerTransferValues(testContext, externalAssetOwnerTransferArgumentCaptor.getValue(),
|
||||
ExternalTransferStatus.PENDING_INTERMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntermediarySaleLoanByLoanIdDelayedSettlementIsNotEnabled() {
|
||||
TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(false);
|
||||
|
||||
// when
|
||||
ExternalAssetOwnerInitiateTransferException thrownException = Assert.assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.intermediarySaleLoanByLoanId(command));
|
||||
|
||||
// then
|
||||
verify(testContext.externalAssetOwnerTransferRepository, times(0)).saveAndFlush(any(ExternalAssetOwnerTransfer.class));
|
||||
verify(testContext.externalAssetOwnerRepository, times(0)).saveAndFlush(any(ExternalAssetOwner.class));
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
Assertions.assertEquals(thrownException.getMessage(), "Delayed Settlement Configuration is not enabled for the loan product: "
|
||||
+ testContext.loanDataForExternalTransfer.getLoanProductShortName());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("effectiveTransferDataProviderIntermediarySaleTests")
|
||||
public void testValidateEffectiveTransferForIntermediarySale(final String testName,
|
||||
final List<ExternalAssetOwnerTransfer> externalAssetOwnerTransferList, final String expectedErrorString) {
|
||||
TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
when(testContext.externalAssetOwnerTransferRepository.findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class))).thenReturn(externalAssetOwnerTransferList);
|
||||
|
||||
// when
|
||||
ExternalAssetOwnerInitiateTransferException thrownException = Assert.assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.intermediarySaleLoanByLoanId(command));
|
||||
|
||||
// then
|
||||
verify(testContext.externalAssetOwnerTransferRepository, times(0)).saveAndFlush(any(ExternalAssetOwnerTransfer.class));
|
||||
verify(testContext.externalAssetOwnerRepository, times(0)).saveAndFlush(any(ExternalAssetOwner.class));
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
Assertions.assertEquals(thrownException.getMessage(), expectedErrorString);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("loanStatusValidationDataProviderValidActive")
|
||||
public void testValidateValidActiveLoanStatus(final String testName, final LoanStatus loanStatus) {
|
||||
TestContext testContext = new TestContext();
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.externalAssetOwnerRepository.findByExternalId(any(ExternalId.class)))
|
||||
.thenReturn(Optional.of(testContext.externalAssetOwner));
|
||||
when(testContext.loanDataForExternalTransfer.getLoanStatus()).thenReturn(loanStatus.getValue());
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
|
||||
// when
|
||||
testContext.externalAssetOwnersWriteServiceImpl.intermediarySaleLoanByLoanId(command);
|
||||
|
||||
// then
|
||||
verify(testContext.externalAssetOwnerTransferRepository).saveAndFlush(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
verify(testContext.externalAssetOwnerRepository, times(0)).saveAndFlush(any(ExternalAssetOwner.class));
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
|
||||
assertAssertOwnerTransferValues(testContext, externalAssetOwnerTransferArgumentCaptor.getValue(),
|
||||
ExternalTransferStatus.PENDING_INTERMEDIATE);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("loanStatusValidationDataProviderInvalidActive")
|
||||
public void testValidateInvalidActiveLoanStatus(final String testName, final LoanStatus loanStatus) {
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.loanDataForExternalTransfer.getLoanStatus()).thenReturn(loanStatus.getValue());
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
|
||||
// when
|
||||
ExternalAssetOwnerInitiateTransferException exception = assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.intermediarySaleLoanByLoanId(command));
|
||||
|
||||
assertEquals(exception.getMessage(), String.format("Loan status %s is not valid for transfer.", loanStatus.name()));
|
||||
|
||||
// then
|
||||
verify(testContext.fromApiJsonHelper, times(2)).parse(command.json());
|
||||
verify(testContext.loanRepository, times(1)).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
verify(testContext.externalAssetOwnerTransferRepository, times(1)).exists(any(Specification.class));
|
||||
verifyNoInteractions(testContext.externalAssetOwnerRepository);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("loanStatusValidationDataProviderValidDelayedSettlement")
|
||||
public void testValidateValidDelayedSettlementLoanStatus(final String testName, final LoanStatus loanStatus) {
|
||||
final ExternalAssetOwnerTransfer effectiveTransferForDelayedSettlement = new ExternalAssetOwnerTransfer();
|
||||
effectiveTransferForDelayedSettlement.setStatus(ExternalTransferStatus.ACTIVE_INTERMEDIATE);
|
||||
List<ExternalAssetOwnerTransfer> assetOwnerTransfers = new ArrayList<>();
|
||||
assetOwnerTransfers.add(effectiveTransferForDelayedSettlement);
|
||||
final TestContext testContext = new TestContext();
|
||||
final ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.loanDataForExternalTransfer.getLoanStatus()).thenReturn(loanStatus.getValue());
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.externalAssetOwnerTransferRepository.findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class))).thenReturn(assetOwnerTransfers);
|
||||
|
||||
// when
|
||||
CommandProcessingResult result = testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command);
|
||||
|
||||
// then
|
||||
verify(testContext.externalAssetOwnerRepository).findByExternalId(any(ExternalId.class));
|
||||
verify(testContext.externalAssetOwnerTransferRepository).saveAndFlush(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
verify(testContext.externalAssetOwnerRepository, times(0)).saveAndFlush(any(ExternalAssetOwner.class));
|
||||
verify(testContext.externalAssetOwnerTransferRepository).findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class));
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
|
||||
ExternalAssetOwnerTransfer savedTransfer = externalAssetOwnerTransferArgumentCaptor.getValue();
|
||||
assertAssertOwnerTransferValues(testContext, savedTransfer, ExternalTransferStatus.PENDING);
|
||||
|
||||
assertEquals(savedTransfer.getId(), result.getResourceId());
|
||||
assertEquals(savedTransfer.getExternalId(), result.getResourceExternalId());
|
||||
assertEquals(savedTransfer.getLoanId(), result.getSubResourceId());
|
||||
assertEquals(savedTransfer.getExternalLoanId(), result.getSubResourceExternalId());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("loanStatusValidationDataProviderInvalidDelayedSettlement")
|
||||
public void testValidateInvalidDelayedSettlementLoanStatus(final String testName, final LoanStatus loanStatus) {
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.loanDataForExternalTransfer.getLoanStatus()).thenReturn(loanStatus.getValue());
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
|
||||
// when
|
||||
ExternalAssetOwnerInitiateTransferException exception = assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command));
|
||||
|
||||
assertEquals(exception.getMessage(), String.format("Loan status %s is not valid for transfer.", loanStatus.name()));
|
||||
|
||||
// then
|
||||
verify(testContext.fromApiJsonHelper, times(2)).parse(command.json());
|
||||
verify(testContext.loanRepository, times(1)).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
verify(testContext.externalAssetOwnerTransferRepository, times(1)).exists(any(Specification.class));
|
||||
verifyNoInteractions(testContext.externalAssetOwnerRepository);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("effectiveTransferDataProvider")
|
||||
public void verifyWhenLoanSaleIsInitiatedThenAssetOwnerTransferIsCreated(
|
||||
final List<ExternalAssetOwnerTransfer> externalAssetOwnerTransferList, final boolean isDelayedSettlementEnabled) {
|
||||
final TestContext testContext = new TestContext();
|
||||
final ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(isDelayedSettlementEnabled);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.externalAssetOwnerTransferRepository.findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class))).thenReturn(externalAssetOwnerTransferList);
|
||||
|
||||
// when
|
||||
CommandProcessingResult result = testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command);
|
||||
|
||||
// then
|
||||
verify(testContext.externalAssetOwnerRepository).findByExternalId(any(ExternalId.class));
|
||||
verify(testContext.externalAssetOwnerTransferRepository).saveAndFlush(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
verify(testContext.externalAssetOwnerRepository, times(0)).saveAndFlush(any(ExternalAssetOwner.class));
|
||||
verify(testContext.externalAssetOwnerTransferRepository).findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class));
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
|
||||
ExternalAssetOwnerTransfer savedTransfer = externalAssetOwnerTransferArgumentCaptor.getValue();
|
||||
assertAssertOwnerTransferValues(testContext, savedTransfer, ExternalTransferStatus.PENDING);
|
||||
|
||||
assertEquals(savedTransfer.getId(), result.getResourceId());
|
||||
assertEquals(savedTransfer.getExternalId(), result.getResourceExternalId());
|
||||
assertEquals(savedTransfer.getLoanId(), result.getSubResourceId());
|
||||
assertEquals(savedTransfer.getExternalLoanId(), result.getSubResourceExternalId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateSettlementDateInThePastTest() {
|
||||
TestContext testContext = new TestContext();
|
||||
final JsonElement jsonCommandElement = testContext.fromJsonHelper.parse(testContext.jsonCommand);
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
lenient().when(
|
||||
testContext.fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE, jsonCommandElement))
|
||||
.thenReturn(LocalDate.EPOCH);
|
||||
lenient().when(testContext.fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE,
|
||||
jsonCommandElement, testContext.DATE_FORMAT, Locale.GERMANY)).thenReturn(LocalDate.EPOCH);
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
|
||||
// when
|
||||
ExternalAssetOwnerInitiateTransferException thrownException = Assert.assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.intermediarySaleLoanByLoanId(command));
|
||||
|
||||
// then
|
||||
verify(testContext.externalAssetOwnerTransferRepository, times(0)).saveAndFlush(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
verify(testContext.externalAssetOwnerRepository, times(0)).saveAndFlush(any(ExternalAssetOwner.class));
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
Assertions.assertEquals(thrownException.getMessage(), "Settlement date cannot be in the past");
|
||||
}
|
||||
|
||||
private static Stream<Arguments> effectiveTransferDataProvider() {
|
||||
final ExternalAssetOwnerTransfer effectiveTransferForDelayedSettlement = new ExternalAssetOwnerTransfer();
|
||||
effectiveTransferForDelayedSettlement.setStatus(ExternalTransferStatus.ACTIVE_INTERMEDIATE);
|
||||
|
||||
return Stream.of(Arguments.of(Collections.emptyList(), false), Arguments.of(List.of(effectiveTransferForDelayedSettlement), true));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> effectiveTransferDataProviderIntermediarySaleTests() {
|
||||
final ExternalAssetOwnerTransfer activeIntermediate = new ExternalAssetOwnerTransfer();
|
||||
activeIntermediate.setStatus(ExternalTransferStatus.ACTIVE_INTERMEDIATE);
|
||||
final ExternalAssetOwnerTransfer active = new ExternalAssetOwnerTransfer();
|
||||
active.setStatus(ExternalTransferStatus.ACTIVE);
|
||||
final ExternalAssetOwnerTransfer pendingIntermediate = new ExternalAssetOwnerTransfer();
|
||||
pendingIntermediate.setStatus(ExternalTransferStatus.PENDING_INTERMEDIATE);
|
||||
|
||||
return Stream.of(
|
||||
Arguments.of("Incorrect State", List.of(activeIntermediate),
|
||||
String.format("This loan cannot be sold, because it is incorrect state! (transferId = %s)",
|
||||
activeIntermediate.getId())),
|
||||
Arguments.of("Already In Progress", List.of(activeIntermediate, active),
|
||||
"This loan cannot be sold, there is already an in progress transfer"),
|
||||
Arguments.of("Already Pending Intermediary", List.of(pendingIntermediate),
|
||||
"External asset owner transfer is already in PENDING_INTERMEDIATE state for this loan"),
|
||||
Arguments.of("Already Owned by External Asset Owner", List.of(active),
|
||||
"This loan cannot be sold, because it is owned by an external asset owner"));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> loanStatusValidationDataProviderValidActive() {
|
||||
return Stream.of(Arguments.of("Active Loan Status", LoanStatus.ACTIVE),
|
||||
Arguments.of("Transfer In Progress Loan Status", LoanStatus.TRANSFER_IN_PROGRESS),
|
||||
Arguments.of("Transfer On Hold Loan Status", LoanStatus.TRANSFER_ON_HOLD));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> loanStatusValidationDataProviderValidDelayedSettlement() {
|
||||
return Stream.of(Arguments.of("Active Loan Status", LoanStatus.ACTIVE),
|
||||
Arguments.of("Transfer On Hold Loan Status", LoanStatus.TRANSFER_ON_HOLD),
|
||||
Arguments.of("Transfer In Progress Status", LoanStatus.TRANSFER_IN_PROGRESS),
|
||||
Arguments.of("Overpaid Loan Status", LoanStatus.OVERPAID),
|
||||
Arguments.of("Closed Obligations Met Loan Status", LoanStatus.CLOSED_OBLIGATIONS_MET));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> loanStatusValidationDataProviderInvalidActive() {
|
||||
return Stream.of(Arguments.of("Invalid Loan Status", LoanStatus.INVALID), Arguments.of("Overpaid Loan Status", LoanStatus.OVERPAID),
|
||||
Arguments.of("Approved Loan Status", LoanStatus.APPROVED), Arguments.of("Rejected Loan Status", LoanStatus.REJECTED),
|
||||
Arguments.of("Submitted and Pending Approval Loan Status", LoanStatus.SUBMITTED_AND_PENDING_APPROVAL),
|
||||
Arguments.of("Withdrawn By Client Loan Status", LoanStatus.WITHDRAWN_BY_CLIENT),
|
||||
Arguments.of("Closed Written Off Loan Status", LoanStatus.CLOSED_WRITTEN_OFF),
|
||||
Arguments.of("Closed Reschedule Outstanding Amount Loan Status", LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT),
|
||||
Arguments.of("Closed Obligations Met Loan Status", LoanStatus.CLOSED_OBLIGATIONS_MET));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> loanStatusValidationDataProviderInvalidDelayedSettlement() {
|
||||
return Stream.of(Arguments.of("Invalid Loan Status", LoanStatus.INVALID), Arguments.of("Approved Loan Status", LoanStatus.APPROVED),
|
||||
Arguments.of("Rejected Loan Status", LoanStatus.REJECTED),
|
||||
Arguments.of("Submitted and Pending Approval Loan Status", LoanStatus.SUBMITTED_AND_PENDING_APPROVAL),
|
||||
Arguments.of("Withdrawn By Client Loan Status", LoanStatus.WITHDRAWN_BY_CLIENT),
|
||||
Arguments.of("Closed Written Off Loan Status", LoanStatus.CLOSED_WRITTEN_OFF),
|
||||
Arguments.of("Closed Reschedule Outstanding Amount Loan Status", LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyWhenLoanSaleIsInitiatedWithoutOwnerThenAssetOwnerTransferIsCreated() {
|
||||
final TestContext testContext = new TestContext();
|
||||
final ArgumentCaptor<ExternalAssetOwnerTransfer> externalAssetOwnerTransferArgumentCaptor = ArgumentCaptor
|
||||
.forClass(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(false);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.externalAssetOwnerRepository.findByExternalId(any(ExternalId.class))).thenReturn(Optional.empty());
|
||||
|
||||
// when
|
||||
CommandProcessingResult result = testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command);
|
||||
|
||||
// then
|
||||
verify(testContext.externalAssetOwnerRepository).findByExternalId(any(ExternalId.class));
|
||||
verify(testContext.externalAssetOwnerTransferRepository).saveAndFlush(externalAssetOwnerTransferArgumentCaptor.capture());
|
||||
verify(testContext.externalAssetOwnerRepository).saveAndFlush(any(ExternalAssetOwner.class));
|
||||
verify(testContext.externalAssetOwnerTransferRepository).findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class));
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
|
||||
ExternalAssetOwnerTransfer savedTransfer = externalAssetOwnerTransferArgumentCaptor.getValue();
|
||||
assertAssertOwnerTransferValues(testContext, savedTransfer, ExternalTransferStatus.PENDING);
|
||||
|
||||
assertEquals(savedTransfer.getId(), result.getResourceId());
|
||||
assertEquals(savedTransfer.getExternalId(), result.getResourceExternalId());
|
||||
assertEquals(savedTransfer.getLoanId(), result.getSubResourceId());
|
||||
assertEquals(savedTransfer.getExternalLoanId(), result.getSubResourceExternalId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyWhenLoanIsNotFoundThenExceptionIsThrown() {
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId)).thenReturn(Optional.empty());
|
||||
|
||||
// when
|
||||
assertThrows(LoanNotFoundException.class, () -> testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command));
|
||||
|
||||
// then
|
||||
verify(testContext.fromApiJsonHelper, times(1)).parse(command.json());
|
||||
verifyNoMoreInteractions(testContext.loanRepository);
|
||||
verifyNoInteractions(testContext.externalAssetOwnerRepository, testContext.externalAssetOwnerTransferRepository,
|
||||
testContext.delayedSettlementAttributeService);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("invalidFieldsDataProvider")
|
||||
public void verifyWhenFieldValueInvalidThenExceptionIsThrown(final String ownerExternalId, final String transferExternalId,
|
||||
final String purchaseRatio, final LocalDate settlementDate) {
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
final JsonElement jsonCommandElement = testContext.fromJsonHelper.parse(testContext.jsonCommand);
|
||||
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(false);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID, jsonCommandElement))
|
||||
.thenReturn(ownerExternalId);
|
||||
when(testContext.fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, jsonCommandElement))
|
||||
.thenReturn(transferExternalId);
|
||||
when(testContext.fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO, jsonCommandElement))
|
||||
.thenReturn(purchaseRatio);
|
||||
when(testContext.fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE, jsonCommandElement))
|
||||
.thenReturn(settlementDate);
|
||||
|
||||
// when
|
||||
assertThrows(PlatformApiDataValidationException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command));
|
||||
|
||||
// then
|
||||
verify(testContext.fromApiJsonHelper, times(2)).parse(command.json());
|
||||
verifyNoMoreInteractions(testContext.loanRepository);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
verifyNoInteractions(testContext.externalAssetOwnerRepository, testContext.externalAssetOwnerTransferRepository);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> invalidFieldsDataProvider() {
|
||||
// ownerExternalId, transferExternalId, purchaseRatio, settlementDate
|
||||
return Stream.of(
|
||||
// settlement date cannot be null
|
||||
Arguments.of("value", "value", "value", null),
|
||||
// purchaseRatio cannot be null
|
||||
Arguments.of("value", "value", null, LocalDate.now(ZoneId.systemDefault()).plusDays(1)),
|
||||
// purchaseRatio length cannot be > 50
|
||||
Arguments.of("value", "value", RandomStringUtils.randomAlphanumeric(51), LocalDate.now(ZoneId.systemDefault()).plusDays(1)),
|
||||
// transferExternalId length cannot be > 100
|
||||
Arguments.of("value", RandomStringUtils.randomAlphanumeric(101), "value",
|
||||
LocalDate.now(ZoneId.systemDefault()).plusDays(1)),
|
||||
// ownerExternalId cannot be null
|
||||
Arguments.of(null, "value", "value", LocalDate.now(ZoneId.systemDefault()).plusDays(1)),
|
||||
// ownerExternalId length cannot be > 100
|
||||
Arguments.of(RandomStringUtils.randomAlphanumeric(101), "value", "value",
|
||||
LocalDate.now(ZoneId.systemDefault()).plusDays(1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyWhenTransferExternalIdExistsThenExceptionIsThrown() {
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(false);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.externalAssetOwnerTransferRepository.exists(any(Specification.class))).thenReturn(true);
|
||||
|
||||
// when
|
||||
assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command));
|
||||
|
||||
// then
|
||||
verify(testContext.fromApiJsonHelper, times(2)).parse(command.json());
|
||||
verify(testContext.loanRepository, times(1)).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
verify(testContext.externalAssetOwnerTransferRepository, times(1)).exists(any(Specification.class));
|
||||
verifyNoInteractions(testContext.externalAssetOwnerRepository);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("delayedSettlementFlagDataProvider")
|
||||
public void verifyWhenTooManyEffectiveTransfersThenExceptionIsThrown(final boolean isDelayedSettlementEnabled) {
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(isDelayedSettlementEnabled);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.externalAssetOwnerTransferRepository.findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class))).thenReturn(List.of(new ExternalAssetOwnerTransfer(), new ExternalAssetOwnerTransfer()));
|
||||
|
||||
// when
|
||||
ExternalAssetOwnerInitiateTransferException exception = assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command));
|
||||
|
||||
assertEquals(exception.getMessage(), "This loan cannot be sold, there is already an in progress transfer");
|
||||
|
||||
// then
|
||||
verify(testContext.fromApiJsonHelper, times(2)).parse(command.json());
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
verify(testContext.externalAssetOwnerTransferRepository).exists(any(Specification.class));
|
||||
verify(testContext.externalAssetOwnerTransferRepository, times(1)).findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class));
|
||||
verify(testContext.externalAssetOwnerRepository).findByExternalId(any(ExternalId.class));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> delayedSettlementFlagDataProvider() {
|
||||
return Stream.of(Arguments.of(false), Arguments.of(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyWhenNoEffectiveTransfersWithDelayedSettlementThenExceptionIsThrown() {
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.externalAssetOwnerTransferRepository.findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class))).thenReturn(Collections.emptyList());
|
||||
|
||||
// when
|
||||
ExternalAssetOwnerInitiateTransferException exception = assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command));
|
||||
|
||||
assertEquals(exception.getMessage(), "This loan cannot be sold, no effective transfer found.");
|
||||
// then
|
||||
verify(testContext.fromApiJsonHelper, times(2)).parse(command.json());
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
verify(testContext.externalAssetOwnerTransferRepository).exists(any(Specification.class));
|
||||
verify(testContext.externalAssetOwnerTransferRepository, times(1)).findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class));
|
||||
verify(testContext.externalAssetOwnerRepository).findByExternalId(any(ExternalId.class));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("invalidTransferStatusDataProvider")
|
||||
public void verifyWhenInvalidTransferStatusThenExceptionIsThrown(final ExternalTransferStatus externalTransferStatus,
|
||||
final boolean isDelayedSettlementEnabled, final String expectedExceptionMessage) {
|
||||
final TestContext testContext = new TestContext();
|
||||
|
||||
// given
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
final ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
externalAssetOwnerTransfer.setStatus(externalTransferStatus);
|
||||
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(isDelayedSettlementEnabled);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
when(testContext.externalAssetOwnerTransferRepository.findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class))).thenReturn(List.of(externalAssetOwnerTransfer));
|
||||
|
||||
// when
|
||||
ExternalAssetOwnerInitiateTransferException exception = assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.saleLoanByLoanId(command));
|
||||
|
||||
assertEquals(exception.getMessage(), expectedExceptionMessage);
|
||||
|
||||
// then
|
||||
verify(testContext.fromApiJsonHelper, times(2)).parse(command.json());
|
||||
verify(testContext.loanRepository).findLoanDataForExternalTransferByLoanId(testContext.loanId);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
verify(testContext.externalAssetOwnerTransferRepository).exists(any(Specification.class));
|
||||
verify(testContext.externalAssetOwnerTransferRepository, times(1)).findEffectiveTransfersOrderByIdDesc(eq(testContext.loanId),
|
||||
any(LocalDate.class));
|
||||
verify(testContext.externalAssetOwnerRepository).findByExternalId(any(ExternalId.class));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> invalidTransferStatusDataProvider() {
|
||||
return Stream.of(
|
||||
Arguments.of(ExternalTransferStatus.PENDING, false,
|
||||
"External asset owner transfer is already in PENDING state for this loan"),
|
||||
Arguments.of(ExternalTransferStatus.ACTIVE, false,
|
||||
"This loan cannot be sold, because it is owned by an external asset owner"),
|
||||
Arguments.of(ExternalTransferStatus.PENDING_INTERMEDIATE, true,
|
||||
"This loan cannot be sold, because it is not in ACTIVE-INTERMEDIATE state."),
|
||||
Arguments.of(ExternalTransferStatus.ACTIVE, true,
|
||||
"This loan cannot be sold, because it is not in ACTIVE-INTERMEDIATE state."),
|
||||
Arguments.of(ExternalTransferStatus.DECLINED, true,
|
||||
"This loan cannot be sold, because it is not in ACTIVE-INTERMEDIATE state."),
|
||||
Arguments.of(ExternalTransferStatus.PENDING, true,
|
||||
"This loan cannot be sold, because it is not in ACTIVE-INTERMEDIATE state."),
|
||||
Arguments.of(ExternalTransferStatus.BUYBACK, true,
|
||||
"This loan cannot be sold, because it is not in ACTIVE-INTERMEDIATE state."),
|
||||
Arguments.of(ExternalTransferStatus.CANCELLED, true,
|
||||
"This loan cannot be sold, because it is not in ACTIVE-INTERMEDIATE state."));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("buybackValidationWithDelaySettlementSuccessfulDataProvider")
|
||||
void buybackLoanByLoanIdWhenDelaySettlementEnabledSuccess(final List<ExternalTransferStatus> transferStatuses,
|
||||
final ExternalTransferStatus expectedStatus) {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
|
||||
List<ExternalAssetOwnerTransfer> transfers = transferStatuses.stream()
|
||||
.map(transferStatus -> createExternalAssetOwnerTransfer(testContext, transferStatus)).toList();
|
||||
when(testContext.externalAssetOwnerTransferRepository.findEffectiveTransfersOrderByIdDesc(testContext.loanId, actualDate))
|
||||
.thenReturn(transfers);
|
||||
|
||||
// when
|
||||
CommandProcessingResult result = testContext.externalAssetOwnersWriteServiceImpl.buybackLoanByLoanId(command);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<ExternalAssetOwnerTransfer> savedTransferCaptor = ArgumentCaptor.forClass(ExternalAssetOwnerTransfer.class);
|
||||
verify(testContext.externalAssetOwnerTransferRepository).saveAndFlush(savedTransferCaptor.capture());
|
||||
|
||||
ExternalAssetOwnerTransfer savedTransfer = savedTransferCaptor.getValue();
|
||||
assertNotNull(savedTransfer);
|
||||
assertFalse(transfers.isEmpty());
|
||||
ExternalAssetOwnerTransfer expectedEffectiveTransfer = transfers.get(0);
|
||||
assertEquals(testContext.transferExternalId, savedTransfer.getExternalId().getValue());
|
||||
assertEquals(expectedEffectiveTransfer.getOwner(), savedTransfer.getOwner());
|
||||
assertEquals(expectedStatus, savedTransfer.getStatus());
|
||||
assertEquals(expectedEffectiveTransfer.getLoanId(), savedTransfer.getLoanId());
|
||||
assertEquals(expectedEffectiveTransfer.getExternalLoanId(), savedTransfer.getExternalLoanId());
|
||||
assertEquals(testContext.settlementDate, savedTransfer.getSettlementDate());
|
||||
assertEquals(actualDate, savedTransfer.getEffectiveDateFrom());
|
||||
assertEquals(FUTURE_DATE_9999_12_31, savedTransfer.getEffectiveDateTo());
|
||||
assertEquals(expectedEffectiveTransfer.getPurchasePriceRatio(), savedTransfer.getPurchasePriceRatio());
|
||||
|
||||
assertEquals(savedTransfer.getId(), result.getResourceId());
|
||||
assertEquals(savedTransfer.getExternalId(), result.getResourceExternalId());
|
||||
assertEquals(savedTransfer.getLoanId(), result.getSubResourceId());
|
||||
assertEquals(savedTransfer.getExternalLoanId(), result.getSubResourceExternalId());
|
||||
|
||||
verifyNoInteractions(testContext.externalAssetOwnerRepository);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> buybackValidationWithDelaySettlementSuccessfulDataProvider() {
|
||||
return Stream.of(Arguments.of(List.of(ExternalTransferStatus.ACTIVE_INTERMEDIATE), ExternalTransferStatus.BUYBACK_INTERMEDIATE),
|
||||
Arguments.of(List.of(ExternalTransferStatus.ACTIVE), ExternalTransferStatus.BUYBACK));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("buybackValidationWithDelaySettlementFailureDataProvider")
|
||||
void buybackLoanByLoanIdWhenDelaySettlementEnabledFailure(final List<ExternalTransferStatus> transferStatuses,
|
||||
final String expectedExceptionMessage) {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
|
||||
final JsonCommand command = createJsonCommand(testContext.jsonCommand, testContext.loanId);
|
||||
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(testContext.loanProductId)).thenReturn(true);
|
||||
when(testContext.loanRepository.findLoanDataForExternalTransferByLoanId(testContext.loanId))
|
||||
.thenReturn(Optional.of(testContext.loanDataForExternalTransfer));
|
||||
|
||||
List<ExternalAssetOwnerTransfer> transfers = transferStatuses.stream()
|
||||
.map(transferStatus -> createExternalAssetOwnerTransfer(testContext, transferStatus)).toList();
|
||||
when(testContext.externalAssetOwnerTransferRepository.findEffectiveTransfersOrderByIdDesc(testContext.loanId, actualDate))
|
||||
.thenReturn(transfers);
|
||||
|
||||
// when
|
||||
ExternalAssetOwnerInitiateTransferException actualException = assertThrows(ExternalAssetOwnerInitiateTransferException.class,
|
||||
() -> testContext.externalAssetOwnersWriteServiceImpl.buybackLoanByLoanId(command));
|
||||
|
||||
// then
|
||||
assertEquals(expectedExceptionMessage, actualException.getMessage());
|
||||
verify(testContext.externalAssetOwnerTransferRepository, never()).saveAndFlush(any());
|
||||
verifyNoInteractions(testContext.externalAssetOwnerRepository);
|
||||
verify(testContext.delayedSettlementAttributeService).isEnabled(testContext.loanProductId);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> buybackValidationWithDelaySettlementFailureDataProvider() {
|
||||
return Stream.of(
|
||||
Arguments.of(Collections.emptyList(), "This loan cannot be bought back, it is not owned by an external asset owner"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.PENDING_INTERMEDIATE),
|
||||
"This loan cannot be bought back, effective transfer is not in right state: PENDING_INTERMEDIATE"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.PENDING),
|
||||
"This loan cannot be bought back, effective transfer is not in right state: PENDING"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.DECLINED),
|
||||
"This loan cannot be bought back, effective transfer is not in right state: DECLINED"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.BUYBACK),
|
||||
"This loan cannot be bought back, effective transfer is not in right state: BUYBACK"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.BUYBACK_INTERMEDIATE),
|
||||
"This loan cannot be bought back, effective transfer is not in right state: BUYBACK_INTERMEDIATE"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.CANCELLED),
|
||||
"This loan cannot be bought back, effective transfer is not in right state: CANCELLED"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.ACTIVE_INTERMEDIATE, ExternalTransferStatus.PENDING),
|
||||
"This loan cannot be bought back, external asset owner sale is pending"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.PENDING, ExternalTransferStatus.ACTIVE_INTERMEDIATE),
|
||||
"This loan cannot be bought back, external asset owner sale is pending"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.ACTIVE_INTERMEDIATE, ExternalTransferStatus.BUYBACK_INTERMEDIATE),
|
||||
"This loan cannot be bought back, external asset owner buyback transfer is already in progress"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.BUYBACK_INTERMEDIATE, ExternalTransferStatus.ACTIVE_INTERMEDIATE),
|
||||
"This loan cannot be bought back, external asset owner buyback transfer is already in progress"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.ACTIVE, ExternalTransferStatus.BUYBACK),
|
||||
"This loan cannot be bought back, external asset owner buyback transfer is already in progress"),
|
||||
Arguments.of(List.of(ExternalTransferStatus.BUYBACK, ExternalTransferStatus.ACTIVE),
|
||||
"This loan cannot be bought back, external asset owner buyback transfer is already in progress"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create {@link JsonCommand} object from json command string.
|
||||
*
|
||||
* @param jsonCommand
|
||||
* the json command string
|
||||
* @param loanId
|
||||
* the loan id
|
||||
* @return the {@link JsonCommand} object.
|
||||
*/
|
||||
private JsonCommand createJsonCommand(final String jsonCommand, final Long loanId) {
|
||||
return new JsonCommand(null, jsonCommand, null, null, null, null, null, null, null, loanId, null, null, null, null, null, null,
|
||||
null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create {@link ExternalAssetOwnerTransfer} object.
|
||||
*
|
||||
* @param status
|
||||
* the {@link ExternalTransferStatus}
|
||||
* @return the {@link ExternalAssetOwnerTransfer} object.
|
||||
*/
|
||||
private ExternalAssetOwnerTransfer createExternalAssetOwnerTransfer(final TestContext testContext,
|
||||
final ExternalTransferStatus status) {
|
||||
ExternalAssetOwnerTransfer transfer = new ExternalAssetOwnerTransfer();
|
||||
transfer.setExternalId(new ExternalId(RandomStringUtils.randomAlphanumeric(10)));
|
||||
transfer.setOwner(new ExternalAssetOwner());
|
||||
transfer.setStatus(status);
|
||||
transfer.setLoanId(testContext.loanId);
|
||||
transfer.setExternalLoanId(new ExternalId(testContext.externalLoanId));
|
||||
transfer.setExternalGroupId(new ExternalId(testContext.externalLoanId));
|
||||
transfer.setPurchasePriceRatio(TestContext.PURCHASE_RATIO.toString());
|
||||
|
||||
return transfer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts on the {@link ExternalAssetOwnerTransfer} object values against test values.
|
||||
*
|
||||
* @param testContext
|
||||
* the test context with expected values.
|
||||
* @param externalAssetOwnerTransfer
|
||||
* the {@link ExternalAssetOwnerTransfer} object.
|
||||
* @param expectedTransferStatus
|
||||
* the expected transfer status.
|
||||
*/
|
||||
private void assertAssertOwnerTransferValues(final TestContext testContext, final ExternalAssetOwnerTransfer externalAssetOwnerTransfer,
|
||||
final ExternalTransferStatus expectedTransferStatus) {
|
||||
assertEquals(testContext.loanId, externalAssetOwnerTransfer.getLoanId());
|
||||
assertEquals(testContext.externalLoanId, externalAssetOwnerTransfer.getExternalLoanId().getValue());
|
||||
assertEquals(testContext.ownerExternalId, externalAssetOwnerTransfer.getOwner().getExternalId().getValue());
|
||||
assertEquals(testContext.transferExternalId, externalAssetOwnerTransfer.getExternalId().getValue());
|
||||
assertEquals(testContext.transferExternalGroupId, externalAssetOwnerTransfer.getExternalGroupId().getValue());
|
||||
assertEquals(expectedTransferStatus, externalAssetOwnerTransfer.getStatus());
|
||||
assertEquals(TestContext.PURCHASE_RATIO.toString(), externalAssetOwnerTransfer.getPurchasePriceRatio());
|
||||
assertEquals(testContext.settlementDate, externalAssetOwnerTransfer.getSettlementDate());
|
||||
assertEquals(actualDate, externalAssetOwnerTransfer.getEffectiveDateFrom());
|
||||
assertEquals(FUTURE_DATE_9999_12_31, externalAssetOwnerTransfer.getEffectiveDateTo());
|
||||
}
|
||||
|
||||
@SuppressFBWarnings({ "VA_FORMAT_STRING_USES_NEWLINE" })
|
||||
static class TestContext {
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerRepository externalAssetOwnerRepository;
|
||||
|
||||
@Mock
|
||||
private FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Mock
|
||||
private LoanRepository loanRepository;
|
||||
|
||||
@Mock
|
||||
private DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||
|
||||
@Mock
|
||||
private LoanDataForExternalTransfer loanDataForExternalTransfer;
|
||||
|
||||
@InjectMocks
|
||||
private ExternalAssetOwnersWriteServiceImpl externalAssetOwnersWriteServiceImpl;
|
||||
|
||||
private static final BigDecimal PURCHASE_RATIO = BigDecimal.valueOf(Float.parseFloat(RandomStringUtils.randomNumeric(1, 3)) / 100)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
private static final String DATE_FORMAT = "yyyy-MM-dd";
|
||||
private static final String LOCALE = "de_DE";
|
||||
|
||||
private final FromJsonHelper fromJsonHelper = new FromJsonHelper();
|
||||
private final ExternalAssetOwner externalAssetOwner = new ExternalAssetOwner();
|
||||
private final Long loanId = Long.valueOf(RandomStringUtils.randomNumeric(2));
|
||||
private final String externalLoanId = RandomStringUtils.randomAlphanumeric(10);
|
||||
private final Long loanProductId = Long.valueOf(RandomStringUtils.randomNumeric(2));
|
||||
private final String loanProductShortName = RandomStringUtils.randomAlphanumeric(10);
|
||||
private final String ownerExternalId = RandomStringUtils.randomAlphanumeric(10);
|
||||
private final String transferExternalId = RandomStringUtils.randomAlphanumeric(10);
|
||||
private final String transferExternalGroupId = RandomStringUtils.randomAlphanumeric(10);
|
||||
private final LocalDate settlementDate = LocalDate.parse("9999-08-22");
|
||||
private final String jsonCommand = String.format("""
|
||||
{
|
||||
"settlementDate": "%s",
|
||||
"ownerExternalId": "%s",
|
||||
"transferExternalId": "%s",
|
||||
"transferExternalGroupId": "%s",
|
||||
"purchasePriceRatio": "%s",
|
||||
"dateFormat": "%s",
|
||||
"locale": "%s"
|
||||
}
|
||||
""", settlementDate, ownerExternalId, transferExternalId, transferExternalGroupId, PURCHASE_RATIO, DATE_FORMAT, LOCALE);
|
||||
|
||||
@SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
|
||||
TestContext() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
final JsonElement jsonCommandElement = fromJsonHelper.parse(jsonCommand);
|
||||
externalAssetOwner.setExternalId(new ExternalId(ownerExternalId));
|
||||
|
||||
lenient().when(fromApiJsonHelper.parse(anyString())).thenReturn(jsonCommandElement);
|
||||
lenient().when(fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.OWNER_EXTERNAL_ID, jsonCommandElement))
|
||||
.thenReturn(ownerExternalId);
|
||||
lenient().when(fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_ID, jsonCommandElement))
|
||||
.thenReturn(transferExternalId);
|
||||
lenient().when(
|
||||
fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.TRANSFER_EXTERNAL_GROUP_ID, jsonCommandElement))
|
||||
.thenReturn(transferExternalGroupId);
|
||||
lenient().when(fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.PURCHASE_PRICE_RATIO, jsonCommandElement))
|
||||
.thenReturn(PURCHASE_RATIO.toString());
|
||||
lenient().when(fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.DATEFORMAT, jsonCommandElement))
|
||||
.thenReturn(DATE_FORMAT);
|
||||
lenient().when(fromApiJsonHelper.extractStringNamed(ExternalTransferRequestParameters.LOCALE, jsonCommandElement))
|
||||
.thenReturn(LOCALE);
|
||||
lenient().when(fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE, jsonCommandElement))
|
||||
.thenReturn(settlementDate);
|
||||
lenient().when(fromApiJsonHelper.extractLocalDateNamed(ExternalTransferRequestParameters.SETTLEMENT_DATE, jsonCommandElement,
|
||||
DATE_FORMAT, Locale.GERMANY)).thenReturn(settlementDate);
|
||||
|
||||
lenient().when(externalAssetOwnerRepository.findByExternalId(any(ExternalId.class)))
|
||||
.thenReturn(Optional.of(externalAssetOwner));
|
||||
lenient().when(externalAssetOwnerTransferRepository.findEffectiveTransfersOrderByIdDesc(eq(loanId), any(LocalDate.class)))
|
||||
.thenReturn(Collections.emptyList());
|
||||
lenient().when(externalAssetOwnerRepository.saveAndFlush(any(ExternalAssetOwner.class))).thenReturn(externalAssetOwner);
|
||||
|
||||
lenient().when(loanDataForExternalTransfer.getId()).thenReturn(loanId);
|
||||
lenient().when(loanDataForExternalTransfer.getExternalId()).thenReturn(new ExternalId(externalLoanId));
|
||||
lenient().when(loanDataForExternalTransfer.getLoanStatus()).thenReturn(LoanStatus.ACTIVE.getValue());
|
||||
lenient().when(loanDataForExternalTransfer.getLoanProductId()).thenReturn(loanProductId);
|
||||
lenient().when(loanDataForExternalTransfer.getLoanProductShortName()).thenReturn(loanProductShortName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
-11
@@ -19,8 +19,6 @@
|
||||
package org.apache.fineract.investor.service;
|
||||
|
||||
import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.BUYBACK;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -36,10 +34,12 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
|
||||
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.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
|
||||
@@ -50,6 +50,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
@@ -80,38 +83,88 @@ public class LoanAccountOwnerTransferServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyWhenCancelPendingSaleAndBuybackTransferThenBusinessEventsAreSent() {
|
||||
public void verifyWhenCancelPendingAndIntermediateSaleAndBuybackTransferThenBusinessEventsAreSent() {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
|
||||
ExternalAssetOwnerTransfer pendingSaleTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer pendingBuybackTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer pendingIntermediateTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer pendingBuyBackIntermediateTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer cancelledSaleTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer cancelledBuybackTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer cancelledIntermediateTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer cancelledBuyBackIntermediateSaleTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(pendingSaleTransfer, pendingBuybackTransfer);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(pendingSaleTransfer, pendingBuybackTransfer, pendingIntermediateTransfer,
|
||||
pendingBuyBackIntermediateTransfer);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(ASC, "id")))).thenReturn(response);
|
||||
when(externalAssetOwnerTransferRepository.save(any(ExternalAssetOwnerTransfer.class))).thenReturn(pendingSaleTransfer)
|
||||
.thenReturn(cancelledSaleTransfer).thenReturn(pendingBuybackTransfer).thenReturn(cancelledBuybackTransfer);
|
||||
.thenReturn(cancelledSaleTransfer).thenReturn(pendingBuybackTransfer).thenReturn(cancelledBuybackTransfer)
|
||||
.thenReturn(pendingIntermediateTransfer).thenReturn(cancelledIntermediateTransfer)
|
||||
.thenReturn(pendingBuyBackIntermediateTransfer).thenReturn(cancelledBuyBackIntermediateSaleTransfer);
|
||||
|
||||
// when
|
||||
underTest.handleLoanClosedOrOverpaid(loanForProcessing);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(2);
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(4);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, cancelledSaleTransfer);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 1, loanForProcessing, cancelledBuybackTransfer);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 2, loanForProcessing, cancelledIntermediateTransfer);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 3, loanForProcessing, cancelledBuyBackIntermediateSaleTransfer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyWhenDeclinePendingSaleTransferThenBusinessEventIsSent() {
|
||||
public void verifyWhenDeclineCancelPendingAndIntermediateSaleAndBuybackTransferThenBusinessEventsAreSent() {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
|
||||
ExternalAssetOwnerTransfer pendingSaleTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(pendingSaleTransfer.getStatus()).thenReturn(PENDING);
|
||||
when(pendingSaleTransfer.getSettlementDate()).thenReturn(actualDate.minusDays(1));
|
||||
ExternalAssetOwnerTransfer pendingBuybackTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer pendingIntermediateTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer pendingBuyBackIntermediateTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
when(pendingSaleTransfer.getSettlementDate()).thenReturn(actualDate.minusDays(1));
|
||||
when(pendingBuybackTransfer.getSettlementDate()).thenReturn(actualDate.minusDays(1));
|
||||
when(pendingIntermediateTransfer.getSettlementDate()).thenReturn(actualDate);
|
||||
|
||||
ExternalAssetOwnerTransfer declinedSaleTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer cancelledBuybackTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer cancelledIntermediateTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer cancelledBuyBackIntermediateSaleTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(pendingSaleTransfer, pendingBuybackTransfer, pendingIntermediateTransfer,
|
||||
pendingBuyBackIntermediateTransfer);
|
||||
when(externalAssetOwnerTransferRepository.findAll(any(Specification.class), eq(Sort.by(ASC, "id")))).thenReturn(response);
|
||||
when(externalAssetOwnerTransferRepository.save(any(ExternalAssetOwnerTransfer.class))).thenReturn(pendingSaleTransfer)
|
||||
.thenReturn(declinedSaleTransfer).thenReturn(pendingBuybackTransfer).thenReturn(cancelledBuybackTransfer)
|
||||
.thenReturn(pendingIntermediateTransfer).thenReturn(cancelledIntermediateTransfer)
|
||||
.thenReturn(pendingBuyBackIntermediateTransfer).thenReturn(cancelledBuyBackIntermediateSaleTransfer);
|
||||
|
||||
// when
|
||||
underTest.handleLoanClosedOrOverpaid(loanForProcessing);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = verifyBusinessEvents(4);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, pendingSaleTransfer);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 1, loanForProcessing, cancelledBuybackTransfer);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 2, loanForProcessing, cancelledIntermediateTransfer);
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 3, loanForProcessing, cancelledBuyBackIntermediateSaleTransfer);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("pendingStatusDataProvider")
|
||||
public void verifyWhenDeclinePendingSaleTransferThenBusinessEventIsSent(final ExternalTransferStatus pendingStatus) {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
|
||||
ExternalAssetOwnerTransfer pendingSaleTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(pendingSaleTransfer.getStatus()).thenReturn(pendingStatus);
|
||||
ExternalAssetOwnerTransfer declineTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(pendingSaleTransfer);
|
||||
|
||||
@@ -127,8 +180,9 @@ public class LoanAccountOwnerTransferServiceTest {
|
||||
verifyLoanTransferBusinessEvent(businessEventArgumentCaptor, 0, loanForProcessing, declineTransfer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyWhenExecutePendingBuybackTransferThenBusinessEventIsSent() {
|
||||
@ParameterizedTest
|
||||
@MethodSource("buybackStatusDataProvider")
|
||||
public void verifyWhenExecutePendingBuybackTransferThenBusinessEventIsSent(final ExternalTransferStatus buybackStatus) {
|
||||
// given
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
@@ -136,7 +190,7 @@ public class LoanAccountOwnerTransferServiceTest {
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
|
||||
ExternalAssetOwnerTransfer pendingBuybackTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(pendingBuybackTransfer.getStatus()).thenReturn(BUYBACK);
|
||||
when(pendingBuybackTransfer.getStatus()).thenReturn(buybackStatus);
|
||||
ExternalAssetOwnerTransfer activeTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
List<ExternalAssetOwnerTransfer> response = List.of(pendingBuybackTransfer);
|
||||
|
||||
@@ -154,6 +208,14 @@ public class LoanAccountOwnerTransferServiceTest {
|
||||
verifyLoanAccountSnapshotBusinessEvent(businessEventArgumentCaptor, 1, loanForProcessing);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> pendingStatusDataProvider() {
|
||||
return Stream.of(Arguments.of(ExternalTransferStatus.PENDING_INTERMEDIATE), Arguments.of(ExternalTransferStatus.PENDING));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> buybackStatusDataProvider() {
|
||||
return Stream.of(Arguments.of(ExternalTransferStatus.BUYBACK_INTERMEDIATE), Arguments.of(ExternalTransferStatus.BUYBACK));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ArgumentCaptor<BusinessEvent<?>> verifyBusinessEvents(int expectedBusinessEvents) {
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 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.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.stream.Stream;
|
||||
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;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LoanTransferabilityServiceImplTest {
|
||||
|
||||
private static final Long LOAN_PRODUCT_ID = 1L;
|
||||
|
||||
private static Stream<Arguments> amountDataProvider() {
|
||||
return Stream.of(Arguments.of(BigDecimal.ONE, true), Arguments.of(BigDecimal.ZERO, false),
|
||||
Arguments.of(BigDecimal.ONE.negate(), false));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("amountDataProvider")
|
||||
void isTransferableWhenDelayedSettlementDisabled(final BigDecimal loanOutstandingAmount, final boolean expectedResult) {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
|
||||
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.getId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(loanOutstandingAmount);
|
||||
|
||||
Loan loan = Mockito.mock(Loan.class);
|
||||
when(loan.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(loan.getSummary()).thenReturn(loanSummary);
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(LOAN_PRODUCT_ID)).thenReturn(false);
|
||||
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
externalAssetOwnerTransfer.setStatus(ExternalTransferStatus.PENDING);
|
||||
|
||||
// when
|
||||
boolean result = testContext.testSubject.isTransferable(loan, externalAssetOwnerTransfer);
|
||||
|
||||
// then
|
||||
assertEquals(expectedResult, result);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("amountDataProvider")
|
||||
void isTransferableWhenDelayedSettlementEnabledAndSellingToIntermediate(final BigDecimal loanOutstandingAmount,
|
||||
final boolean expectedResult) {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
|
||||
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.getId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(loanOutstandingAmount);
|
||||
|
||||
Loan loan = Mockito.mock(Loan.class);
|
||||
when(loan.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(loan.getSummary()).thenReturn(loanSummary);
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(LOAN_PRODUCT_ID)).thenReturn(true);
|
||||
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
externalAssetOwnerTransfer.setStatus(ExternalTransferStatus.PENDING_INTERMEDIATE);
|
||||
|
||||
// when
|
||||
boolean result = testContext.testSubject.isTransferable(loan, externalAssetOwnerTransfer);
|
||||
|
||||
// then
|
||||
assertEquals(expectedResult, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isTransferableWhenDelayedSettlementEnabledAndSellingToInvestor() {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
|
||||
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
|
||||
when(loanProduct.getId()).thenReturn(LOAN_PRODUCT_ID);
|
||||
|
||||
Loan loan = Mockito.mock(Loan.class);
|
||||
when(loan.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(testContext.delayedSettlementAttributeService.isEnabled(LOAN_PRODUCT_ID)).thenReturn(true);
|
||||
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer = new ExternalAssetOwnerTransfer();
|
||||
externalAssetOwnerTransfer.setStatus(ExternalTransferStatus.PENDING);
|
||||
|
||||
// when
|
||||
boolean result = testContext.testSubject.isTransferable(loan, externalAssetOwnerTransfer);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> declinedSubStatusDataProvider() {
|
||||
return Stream.of(Arguments.of(BigDecimal.ONE, ExternalTransferSubStatus.BALANCE_NEGATIVE),
|
||||
Arguments.of(BigDecimal.ZERO, ExternalTransferSubStatus.BALANCE_ZERO),
|
||||
Arguments.of(null, ExternalTransferSubStatus.BALANCE_ZERO));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("declinedSubStatusDataProvider")
|
||||
void getDeclinedSubStatus(final BigDecimal totalOverpaidAmount, final ExternalTransferSubStatus expectedSubStatus) {
|
||||
// given
|
||||
TestContext testContext = new TestContext();
|
||||
|
||||
Loan loan = Mockito.mock(Loan.class);
|
||||
when(loan.getTotalOverpaid()).thenReturn(totalOverpaidAmount);
|
||||
|
||||
// when
|
||||
ExternalTransferSubStatus result = testContext.testSubject.getDeclinedSubStatus(loan);
|
||||
|
||||
// then
|
||||
assertEquals(expectedSubStatus, result);
|
||||
}
|
||||
|
||||
private static class TestContext {
|
||||
|
||||
@Mock
|
||||
private DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||
|
||||
@InjectMocks
|
||||
private LoanTransferabilityServiceImpl testSubject;
|
||||
|
||||
TestContext() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-8
@@ -19,9 +19,13 @@
|
||||
package org.apache.fineract.investor.service.serialization.serializer.investor;
|
||||
|
||||
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;
|
||||
@@ -67,35 +71,61 @@ public class InvestorBusinessEventSerializerTest {
|
||||
|
||||
@Test
|
||||
public void testSerializationSellOK() {
|
||||
doTest(ACTIVE, null, "SALE", "EXECUTED", null);
|
||||
doTest(ACTIVE, null, null, "SALE", "EXECUTED", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationBuybackOK() {
|
||||
doTest(BUYBACK, null, "BUYBACK", "EXECUTED", null);
|
||||
doTest(BUYBACK, null, null, "BUYBACK", "EXECUTED", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationIntermediarySaleOK() {
|
||||
doTest(ACTIVE_INTERMEDIATE, null, null, "INTERMEDIARYSALE", "EXECUTED", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationBuybackIntermediateOK() {
|
||||
doTest(BUYBACK_INTERMEDIATE, null, null, "BUYBACK", "EXECUTED", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationDeclinedNegativeBalance() {
|
||||
doTest(DECLINED, BALANCE_NEGATIVE, "SALE", "DECLINED", "BALANCE_NEGATIVE");
|
||||
doTest(DECLINED, BALANCE_NEGATIVE, PENDING, "SALE", "DECLINED", "BALANCE_NEGATIVE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationDeclinedBalanceZero() {
|
||||
doTest(DECLINED, BALANCE_ZERO, "SALE", "DECLINED", "BALANCE_ZERO");
|
||||
doTest(DECLINED, BALANCE_ZERO, PENDING, "SALE", "DECLINED", "BALANCE_ZERO");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationDeclinedPendingIntermediateNegativeBalance() {
|
||||
doTest(DECLINED, BALANCE_NEGATIVE, PENDING_INTERMEDIATE, "INTERMEDIARYSALE", "DECLINED", "BALANCE_NEGATIVE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationDeclinedPendingIntermediateBalanceZero() {
|
||||
doTest(DECLINED, BALANCE_ZERO, PENDING_INTERMEDIATE, "INTERMEDIARYSALE", "DECLINED", "BALANCE_ZERO");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationCancelledSameDayTransfer() {
|
||||
doTest(CANCELLED, SAMEDAY_TRANSFERS, "SALE", "CANCELLED", "SAMEDAY_TRANSFERS");
|
||||
doTest(CANCELLED, SAMEDAY_TRANSFERS, PENDING, "SALE", "CANCELLED", "SAMEDAY_TRANSFERS");
|
||||
}
|
||||
|
||||
private void doTest(ExternalTransferStatus status, ExternalTransferSubStatus subStatus, String expectedType, String expectedStatus,
|
||||
String expectedReason) {
|
||||
@Test
|
||||
public void testSerializationCancelledPendingIntermediateSameDayTransfer() {
|
||||
doTest(CANCELLED, SAMEDAY_TRANSFERS, PENDING_INTERMEDIATE, "INTERMEDIARYSALE", "CANCELLED", "SAMEDAY_TRANSFERS");
|
||||
}
|
||||
|
||||
private void doTest(ExternalTransferStatus status, ExternalTransferSubStatus subStatus, ExternalTransferStatus firstTransferStatus,
|
||||
String expectedType, String expectedStatus, String expectedReason) {
|
||||
// given
|
||||
ExternalAssetOwnersReadService mockReadService = Mockito.mock(ExternalAssetOwnersReadService.class);
|
||||
when(mockReadService.retrieveTransferData(123L)).thenReturn(createTransferData(status, subStatus));
|
||||
when(mockReadService.retrieveFirstTransferByExternalId(any(ExternalId.class))).thenReturn(createTransferData(ACTIVE, null));
|
||||
when(mockReadService.retrieveFirstTransferByExternalId(any(ExternalId.class)))
|
||||
.thenReturn(createTransferData(firstTransferStatus, null));
|
||||
Loan loan = Mockito.mock(Loan.class);
|
||||
when(loan.getCurrency()).thenReturn(new MonetaryCurrency("EUR", 2, 1));
|
||||
List<LoanCharge> loanCharges = createMockCharges();
|
||||
|
||||
Reference in New Issue
Block a user