FINERACT-2181: Fix the interest calculation for non active loan
This commit is contained in:
+2
@@ -77,6 +77,8 @@ public final class GlobalConfigurationConstants {
|
|||||||
public static final String ENABLE_PAYMENT_HUB_INTEGRATION = "enable-payment-hub-integration";
|
public static final String ENABLE_PAYMENT_HUB_INTEGRATION = "enable-payment-hub-integration";
|
||||||
public static final String ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY = "enable-immediate-charge-accrual-post-maturity";
|
public static final String ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY = "enable-immediate-charge-accrual-post-maturity";
|
||||||
public static final String ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY = "outstanding-interest-calculation-strategy-for-external-asset-transfer";
|
public static final String ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY = "outstanding-interest-calculation-strategy-for-external-asset-transfer";
|
||||||
|
public static final String ALLOWED_LOAN_STATUSES_FOR_EXTERNAL_ASSET_TRANSFER = "allowed-loan-statuses-for-external-asset-transfer";
|
||||||
|
public static final String ALLOWED_LOAN_STATUSES_OF_DELAYED_SETTLEMENT_FOR_EXTERNAL_ASSET_TRANSFER = "allowed-loan-statuses-of-delayed-settlement-for-external-asset-transfer";
|
||||||
|
|
||||||
private GlobalConfigurationConstants() {}
|
private GlobalConfigurationConstants() {}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -19,12 +19,17 @@
|
|||||||
package org.apache.fineract.infrastructure.configuration.domain;
|
package org.apache.fineract.infrastructure.configuration.domain;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
import org.apache.fineract.infrastructure.cache.domain.CacheType;
|
import org.apache.fineract.infrastructure.cache.domain.CacheType;
|
||||||
|
|
||||||
public interface ConfigurationDomainService {
|
public interface ConfigurationDomainService {
|
||||||
|
|
||||||
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
|
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
|
||||||
|
|
||||||
|
List<String> getAllowedLoanStatusesForExternalAssetTransfer();
|
||||||
|
|
||||||
|
List<String> getAllowedLoanStatusesOfDelayedSettlementForExternalAssetTransfer();
|
||||||
|
|
||||||
boolean isSameMakerCheckerEnabled();
|
boolean isSameMakerCheckerEnabled();
|
||||||
|
|
||||||
boolean isAmazonS3Enabled();
|
boolean isAmazonS3Enabled();
|
||||||
|
|||||||
+5
@@ -49,6 +49,11 @@ public class ExternalAssetOwnerTransferOutstandingInterestCalculation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal calculateOutstandingInterest(Loan loan) {
|
public BigDecimal calculateOutstandingInterest(Loan loan) {
|
||||||
|
// If loan is not active, there should be no outstanding interest
|
||||||
|
if (!loan.isOpen()) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
String outstandingInterestCalculationStrategy = configurationDomainService.getAssetOwnerTransferOustandingInterestStrategy();
|
String outstandingInterestCalculationStrategy = configurationDomainService.getAssetOwnerTransferOustandingInterestStrategy();
|
||||||
return switch (outstandingInterestCalculationStrategy) {
|
return switch (outstandingInterestCalculationStrategy) {
|
||||||
case "TOTAL_OUTSTANDING_INTEREST" -> loan.getSummary().getTotalInterestOutstanding();
|
case "TOTAL_OUTSTANDING_INTEREST" -> loan.getSummary().getTotalInterestOutstanding();
|
||||||
|
|||||||
+15
-11
@@ -21,11 +21,6 @@ package org.apache.fineract.investor.service;
|
|||||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.ACTIVE_INTERMEDIATE;
|
import static org.apache.fineract.investor.data.ExternalTransferStatus.ACTIVE_INTERMEDIATE;
|
||||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING;
|
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING;
|
||||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING_INTERMEDIATE;
|
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING_INTERMEDIATE;
|
||||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.ACTIVE;
|
|
||||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.CLOSED_OBLIGATIONS_MET;
|
|
||||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.OVERPAID;
|
|
||||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.TRANSFER_IN_PROGRESS;
|
|
||||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.TRANSFER_ON_HOLD;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
@@ -43,6 +38,7 @@ import java.util.stream.Collectors;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.cob.data.LoanDataForExternalTransfer;
|
import org.apache.fineract.cob.data.LoanDataForExternalTransfer;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
@@ -74,9 +70,6 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersWriteService {
|
public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersWriteService {
|
||||||
|
|
||||||
private static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
private static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||||
private static final List<LoanStatus> ACTIVE_LOAN_STATUSES = List.of(ACTIVE, TRANSFER_IN_PROGRESS, TRANSFER_ON_HOLD);
|
|
||||||
private static final List<LoanStatus> VALID_DELAYED_SETTLEMENT_LOAN_STATUSES_BUYBACK_AND_SALE = List.of(ACTIVE, TRANSFER_IN_PROGRESS,
|
|
||||||
TRANSFER_ON_HOLD, OVERPAID, CLOSED_OBLIGATIONS_MET);
|
|
||||||
private static final List<ExternalTransferStatus> BUYBACK_READY_STATUSES = List.of(ExternalTransferStatus.PENDING,
|
private static final List<ExternalTransferStatus> BUYBACK_READY_STATUSES = List.of(ExternalTransferStatus.PENDING,
|
||||||
ExternalTransferStatus.ACTIVE);
|
ExternalTransferStatus.ACTIVE);
|
||||||
private static final List<ExternalTransferStatus> BUYBACK_READY_STATUSES_FOR_DELAY_SETTLEMENT = List
|
private static final List<ExternalTransferStatus> BUYBACK_READY_STATUSES_FOR_DELAY_SETTLEMENT = List
|
||||||
@@ -86,6 +79,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
|||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
private final LoanRepository loanRepository;
|
private final LoanRepository loanRepository;
|
||||||
private final DelayedSettlementAttributeService delayedSettlementAttributeService;
|
private final DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||||
|
private final ConfigurationDomainService configurationDomainService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -388,16 +382,16 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
|||||||
|
|
||||||
private void validateLoanStatusIntermediarySale(LoanDataForExternalTransfer loanDataForExternalTransfer) {
|
private void validateLoanStatusIntermediarySale(LoanDataForExternalTransfer loanDataForExternalTransfer) {
|
||||||
LoanStatus loanStatus = loanDataForExternalTransfer.getLoanStatus();
|
LoanStatus loanStatus = loanDataForExternalTransfer.getLoanStatus();
|
||||||
if (!ACTIVE_LOAN_STATUSES.contains(loanStatus)) {
|
if (!getAllowedLoanStatuses().contains(loanStatus)) {
|
||||||
throw new ExternalAssetOwnerInitiateTransferException(String.format("Loan status %s is not valid for transfer.", loanStatus));
|
throw new ExternalAssetOwnerInitiateTransferException(String.format("Loan status %s is not valid for transfer.", loanStatus));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LoanStatus> getValidLoanStatusList(boolean isDelayedSettlementEnabled) {
|
private List<LoanStatus> getValidLoanStatusList(boolean isDelayedSettlementEnabled) {
|
||||||
if (isDelayedSettlementEnabled) {
|
if (isDelayedSettlementEnabled) {
|
||||||
return VALID_DELAYED_SETTLEMENT_LOAN_STATUSES_BUYBACK_AND_SALE;
|
return getAllowedLoanStatusesForDelayedSettlement();
|
||||||
} else {
|
} else {
|
||||||
return ACTIVE_LOAN_STATUSES;
|
return getAllowedLoanStatuses();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,4 +571,14 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
|||||||
externalAssetOwner.setExternalId(ExternalIdFactory.produce(externalId));
|
externalAssetOwner.setExternalId(ExternalIdFactory.produce(externalId));
|
||||||
return externalAssetOwnerRepository.saveAndFlush(externalAssetOwner);
|
return externalAssetOwnerRepository.saveAndFlush(externalAssetOwner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<LoanStatus> getAllowedLoanStatuses() {
|
||||||
|
return configurationDomainService.getAllowedLoanStatusesForExternalAssetTransfer().stream().map(LoanStatus::valueOf)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LoanStatus> getAllowedLoanStatusesForDelayedSettlement() {
|
||||||
|
return configurationDomainService.getAllowedLoanStatusesOfDelayedSettlementForExternalAssetTransfer().stream()
|
||||||
|
.map(LoanStatus::valueOf).collect(Collectors.toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -40,4 +40,5 @@
|
|||||||
<include relativeToChangelogFile="true" file="parts/0016_add_external_reference_id.xml"/>
|
<include relativeToChangelogFile="true" file="parts/0016_add_external_reference_id.xml"/>
|
||||||
<include relativeToChangelogFile="true" file="parts/0017_add_external_asset_owner_loan_product_attr_index.xml"/>
|
<include relativeToChangelogFile="true" file="parts/0017_add_external_asset_owner_loan_product_attr_index.xml"/>
|
||||||
<include relativeToChangelogFile="true" file="parts/0018_add_external_asset_owner_transfer_outstanding_interest_strategy.xml"/>
|
<include relativeToChangelogFile="true" file="parts/0018_add_external_asset_owner_transfer_outstanding_interest_strategy.xml"/>
|
||||||
|
<include relativeToChangelogFile="true" file="parts/0019_add_configurable_allowed_loan_statuses.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||||
|
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||||
|
|
||||||
|
<changeSet author="fineract" id="1">
|
||||||
|
<insert tableName="c_configuration">
|
||||||
|
<column name="name" value="allowed-loan-statuses-for-external-asset-transfer"/>
|
||||||
|
<column name="value"/>
|
||||||
|
<column name="date_value"/>
|
||||||
|
<column name="string_value" value="ACTIVE,TRANSFER_IN_PROGRESS,TRANSFER_ON_HOLD"/>
|
||||||
|
<column name="enabled" valueBoolean="true"/>
|
||||||
|
<column name="is_trap_door" valueBoolean="false"/>
|
||||||
|
<column name="description" value=" Available options: Any combination from LoanStatus enum separated by comma."/>
|
||||||
|
</insert>
|
||||||
|
<insert tableName="c_configuration">
|
||||||
|
<column name="name" value="allowed-loan-statuses-of-delayed-settlement-for-external-asset-transfer"/>
|
||||||
|
<column name="value"/>
|
||||||
|
<column name="date_value"/>
|
||||||
|
<column name="string_value" value="ACTIVE,TRANSFER_IN_PROGRESS,TRANSFER_ON_HOLD,OVERPAID,CLOSED_OBLIGATIONS_MET"/>
|
||||||
|
<column name="enabled" valueBoolean="true"/>
|
||||||
|
<column name="is_trap_door" valueBoolean="false"/>
|
||||||
|
<column name="description" value=" Available options: Any combination from LoanStatus enum separated by comma."/>
|
||||||
|
</insert>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* 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.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
|
import org.apache.fineract.organisation.monetary.mapper.CurrencyMapper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryDataProvider;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanSummaryProviderDelegate;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ExternalAssetOwnerTransferOutstandingInterestCalculationTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanSummaryProviderDelegate loanSummaryDataProvider;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ConfigurationDomainService configurationDomainService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanReadPlatformService loanReadPlatformService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CurrencyMapper currencyMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Loan loan;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanSummary loanSummary;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanAccountData loanAccountData;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanScheduleData loanScheduleData;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanSummaryDataProvider summaryDataProvider;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private ExternalAssetOwnerTransferOutstandingInterestCalculation externalAssetOwnerTransferOutstandingInterestCalculation;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCalculateOutstandingInterest_WhenLoanNotDisbursed_ShouldReturnZero() {
|
||||||
|
// Given
|
||||||
|
when(loan.isOpen()).thenReturn(false);
|
||||||
|
|
||||||
|
// When
|
||||||
|
BigDecimal result = externalAssetOwnerTransferOutstandingInterestCalculation.calculateOutstandingInterest(loan);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(BigDecimal.ZERO, result);
|
||||||
|
|
||||||
|
// Verify that no other calculations were performed
|
||||||
|
verify(configurationDomainService, never()).getAssetOwnerTransferOustandingInterestStrategy();
|
||||||
|
verify(loanReadPlatformService, never()).retrieveOne(Mockito.anyLong());
|
||||||
|
verify(loan, times(1)).isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCalculateOutstandingInterest_WhenLoanDisbursedWithTotalStrategy_ShouldCalculate() {
|
||||||
|
// Given - ACTIVE loan with TOTAL_OUTSTANDING_INTEREST strategy
|
||||||
|
when(loan.isOpen()).thenReturn(true);
|
||||||
|
when(configurationDomainService.getAssetOwnerTransferOustandingInterestStrategy()).thenReturn("TOTAL_OUTSTANDING_INTEREST");
|
||||||
|
when(loan.getSummary()).thenReturn(loanSummary);
|
||||||
|
BigDecimal expectedInterest = new BigDecimal("150.50");
|
||||||
|
when(loanSummary.getTotalInterestOutstanding()).thenReturn(expectedInterest);
|
||||||
|
|
||||||
|
// When
|
||||||
|
BigDecimal result = externalAssetOwnerTransferOutstandingInterestCalculation.calculateOutstandingInterest(loan);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(expectedInterest, result);
|
||||||
|
verify(loan, times(1)).isOpen();
|
||||||
|
verify(loan, times(1)).getSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCalculateOutstandingInterest_BackdatedUndisbursedLoan_ShouldReturnZero() {
|
||||||
|
// Given - backdated loan (created 3 months ago) but not disbursed
|
||||||
|
when(loan.isOpen()).thenReturn(false);
|
||||||
|
|
||||||
|
// When
|
||||||
|
BigDecimal result = externalAssetOwnerTransferOutstandingInterestCalculation.calculateOutstandingInterest(loan);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(BigDecimal.ZERO, result);
|
||||||
|
|
||||||
|
// Verify that the method returned early due to disbursement check
|
||||||
|
verify(loan, times(1)).isOpen();
|
||||||
|
verify(configurationDomainService, never()).getAssetOwnerTransferOustandingInterestStrategy();
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-2
@@ -50,6 +50,7 @@ import java.util.Optional;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.apache.fineract.cob.data.LoanDataForExternalTransfer;
|
import org.apache.fineract.cob.data.LoanDataForExternalTransfer;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
@@ -418,8 +419,8 @@ public class ExternalAssetOwnersWriteServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<Arguments> loanStatusValidationDataProviderInvalidDelayedSettlement() {
|
private static Stream<Arguments> loanStatusValidationDataProviderInvalidDelayedSettlement() {
|
||||||
return Stream.of(Arguments.of("Invalid Loan Status", LoanStatus.INVALID), Arguments.of("Approved Loan Status", LoanStatus.APPROVED),
|
return Stream.of(Arguments.of("Invalid Loan Status", LoanStatus.INVALID), Arguments.of("Rejected Loan Status", LoanStatus.REJECTED),
|
||||||
Arguments.of("Rejected Loan Status", LoanStatus.REJECTED),
|
Arguments.of("Approved Loan Status", LoanStatus.APPROVED),
|
||||||
Arguments.of("Submitted and Pending Approval Loan Status", LoanStatus.SUBMITTED_AND_PENDING_APPROVAL),
|
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("Withdrawn By Client Loan Status", LoanStatus.WITHDRAWN_BY_CLIENT),
|
||||||
Arguments.of("Closed Written Off Loan Status", LoanStatus.CLOSED_WRITTEN_OFF),
|
Arguments.of("Closed Written Off Loan Status", LoanStatus.CLOSED_WRITTEN_OFF),
|
||||||
@@ -860,6 +861,9 @@ public class ExternalAssetOwnersWriteServiceTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private LoanDataForExternalTransfer loanDataForExternalTransfer;
|
private LoanDataForExternalTransfer loanDataForExternalTransfer;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ConfigurationDomainService configurationDomainService;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private ExternalAssetOwnersWriteServiceImpl externalAssetOwnersWriteServiceImpl;
|
private ExternalAssetOwnersWriteServiceImpl externalAssetOwnersWriteServiceImpl;
|
||||||
|
|
||||||
@@ -926,6 +930,11 @@ public class ExternalAssetOwnersWriteServiceTest {
|
|||||||
lenient().when(loanDataForExternalTransfer.getLoanStatus()).thenReturn(LoanStatus.ACTIVE);
|
lenient().when(loanDataForExternalTransfer.getLoanStatus()).thenReturn(LoanStatus.ACTIVE);
|
||||||
lenient().when(loanDataForExternalTransfer.getLoanProductId()).thenReturn(loanProductId);
|
lenient().when(loanDataForExternalTransfer.getLoanProductId()).thenReturn(loanProductId);
|
||||||
lenient().when(loanDataForExternalTransfer.getLoanProductShortName()).thenReturn(loanProductShortName);
|
lenient().when(loanDataForExternalTransfer.getLoanProductShortName()).thenReturn(loanProductShortName);
|
||||||
|
lenient().when(configurationDomainService.getAllowedLoanStatusesForExternalAssetTransfer())
|
||||||
|
.thenReturn(List.of("ACTIVE", "TRANSFER_IN_PROGRESS", "TRANSFER_ON_HOLD"));
|
||||||
|
lenient().when(configurationDomainService.getAllowedLoanStatusesOfDelayedSettlementForExternalAssetTransfer())
|
||||||
|
.thenReturn(List.of("ACTIVE", "TRANSFER_IN_PROGRESS", "TRANSFER_ON_HOLD", "OVERPAID", "CLOSED_OBLIGATIONS_MET"));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -20,6 +20,7 @@ package org.apache.fineract.infrastructure.configuration.domain;
|
|||||||
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -60,6 +61,20 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAllowedLoanStatusesForExternalAssetTransfer() {
|
||||||
|
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(
|
||||||
|
GlobalConfigurationConstants.ALLOWED_LOAN_STATUSES_FOR_EXTERNAL_ASSET_TRANSFER);
|
||||||
|
return List.of(property.getStringValue().split(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAllowedLoanStatusesOfDelayedSettlementForExternalAssetTransfer() {
|
||||||
|
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(
|
||||||
|
GlobalConfigurationConstants.ALLOWED_LOAN_STATUSES_OF_DELAYED_SETTLEMENT_FOR_EXTERNAL_ASSET_TRANSFER);
|
||||||
|
return List.of(property.getStringValue().split(","));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSameMakerCheckerEnabled() {
|
public boolean isSameMakerCheckerEnabled() {
|
||||||
return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.ENABLE_SAME_MAKER_CHECKER).isEnabled();
|
return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.ENABLE_SAME_MAKER_CHECKER).isEnabled();
|
||||||
|
|||||||
+2
-4
@@ -3315,9 +3315,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan);
|
|
||||||
changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus()));
|
|
||||||
|
|
||||||
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
|
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
|
||||||
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
|
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
|
||||||
|
|
||||||
@@ -3359,7 +3356,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||||
|
|
||||||
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
|
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan);
|
||||||
|
changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus()));
|
||||||
return Optional.of(loanTransaction);
|
return Optional.of(loanTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-2
@@ -105,8 +105,8 @@ public class GlobalConfigurationHelper {
|
|||||||
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
|
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
|
||||||
GetGlobalConfigurationsResponse actualGlobalConfigurations = getAllGlobalConfigurations();
|
GetGlobalConfigurationsResponse actualGlobalConfigurations = getAllGlobalConfigurations();
|
||||||
|
|
||||||
Assertions.assertEquals(57, expectedGlobalConfigurations.size());
|
Assertions.assertEquals(59, expectedGlobalConfigurations.size());
|
||||||
Assertions.assertEquals(57, actualGlobalConfigurations.getGlobalConfiguration().size());
|
Assertions.assertEquals(59, actualGlobalConfigurations.getGlobalConfiguration().size());
|
||||||
|
|
||||||
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
|
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
|
||||||
|
|
||||||
@@ -549,6 +549,25 @@ public class GlobalConfigurationHelper {
|
|||||||
assetOwnerTransferInterestOutstandingStrategy.put("string_value", "TOTAL_OUTSTANDING_INTEREST");
|
assetOwnerTransferInterestOutstandingStrategy.put("string_value", "TOTAL_OUTSTANDING_INTEREST");
|
||||||
defaults.add(assetOwnerTransferInterestOutstandingStrategy);
|
defaults.add(assetOwnerTransferInterestOutstandingStrategy);
|
||||||
|
|
||||||
|
HashMap<String, Object> allowedLoanStatusesForExternalAssetTransfer = new HashMap<>();
|
||||||
|
allowedLoanStatusesForExternalAssetTransfer.put("name",
|
||||||
|
GlobalConfigurationConstants.ALLOWED_LOAN_STATUSES_FOR_EXTERNAL_ASSET_TRANSFER);
|
||||||
|
allowedLoanStatusesForExternalAssetTransfer.put("value", 0L);
|
||||||
|
allowedLoanStatusesForExternalAssetTransfer.put("enabled", true);
|
||||||
|
allowedLoanStatusesForExternalAssetTransfer.put("trapDoor", false);
|
||||||
|
allowedLoanStatusesForExternalAssetTransfer.put("string_value", "ACTIVE,TRANSFER_IN_PROGRESS,TRANSFER_ON_HOLD");
|
||||||
|
defaults.add(allowedLoanStatusesForExternalAssetTransfer);
|
||||||
|
|
||||||
|
HashMap<String, Object> allowedLoanStatusesForDelayedSettlementExternalAssetTransfer = new HashMap<>();
|
||||||
|
allowedLoanStatusesForDelayedSettlementExternalAssetTransfer.put("name",
|
||||||
|
GlobalConfigurationConstants.ALLOWED_LOAN_STATUSES_OF_DELAYED_SETTLEMENT_FOR_EXTERNAL_ASSET_TRANSFER);
|
||||||
|
allowedLoanStatusesForDelayedSettlementExternalAssetTransfer.put("value", 0L);
|
||||||
|
allowedLoanStatusesForDelayedSettlementExternalAssetTransfer.put("enabled", true);
|
||||||
|
allowedLoanStatusesForDelayedSettlementExternalAssetTransfer.put("trapDoor", false);
|
||||||
|
allowedLoanStatusesForDelayedSettlementExternalAssetTransfer.put("string_value",
|
||||||
|
"ACTIVE,TRANSFER_IN_PROGRESS,TRANSFER_ON_HOLD,OVERPAID,CLOSED_OBLIGATIONS_MET");
|
||||||
|
defaults.add(allowedLoanStatusesForDelayedSettlementExternalAssetTransfer);
|
||||||
|
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+264
@@ -0,0 +1,264 @@
|
|||||||
|
/**
|
||||||
|
* 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.integrationtests.investor.externalassetowner;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.apache.fineract.client.models.ExternalAssetOwnerRequest;
|
||||||
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
|
import org.apache.fineract.client.models.PostClientsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostInitiateTransferResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||||
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoansResponse;
|
||||||
|
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
|
||||||
|
import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
|
||||||
|
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.ExternalAssetOwnerHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.Account;
|
||||||
|
import org.apache.fineract.integrationtests.common.accounting.FinancialActivityAccountHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
@ExtendWith({ LoanTestLifecycleExtension.class })
|
||||||
|
public class ExternalAssetOwnerTransferUndisbursedLoanTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
|
private Long loan1Id;
|
||||||
|
private Long loan2Id;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setup() {
|
||||||
|
new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", "APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION",
|
||||||
|
"CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES",
|
||||||
|
"EXTERNAL_ASSET_OWNER_TRANSFER");
|
||||||
|
new GlobalConfigurationHelper().updateGlobalConfiguration(
|
||||||
|
GlobalConfigurationConstants.ALLOWED_LOAN_STATUSES_FOR_EXTERNAL_ASSET_TRANSFER,
|
||||||
|
new PutGlobalConfigurationsRequest().stringValue("APPROVED,ACTIVE,TRANSFER_IN_PROGRESS,TRANSFER_ON_HOLD"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void tearDown() {
|
||||||
|
new GlobalConfigurationHelper().updateGlobalConfiguration(
|
||||||
|
GlobalConfigurationConstants.ALLOWED_LOAN_STATUSES_FOR_EXTERNAL_ASSET_TRANSFER,
|
||||||
|
new PutGlobalConfigurationsRequest().stringValue("ACTIVE,TRANSFER_IN_PROGRESS,TRANSFER_ON_HOLD"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExternalAssetOwnerTransferForUndisbursedLoan() {
|
||||||
|
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID, true);
|
||||||
|
|
||||||
|
Account transferAccount = accountHelper.createAssetAccount();
|
||||||
|
FinancialActivityAccountHelper financialActivityAccountHelper = new FinancialActivityAccountHelper(requestSpec);
|
||||||
|
ExternalAssetOwnerHelper externalAssetOwnerHelper = new ExternalAssetOwnerHelper();
|
||||||
|
externalAssetOwnerHelper.setProperFinancialActivity(financialActivityAccountHelper, transferAccount);
|
||||||
|
|
||||||
|
try {
|
||||||
|
runAt("01 January 2024", () -> {
|
||||||
|
PostClientsResponse client = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
Long clientId = client.getClientId();
|
||||||
|
|
||||||
|
PostLoanProductsRequest loanProductRequest = createOnePeriod30DaysPeriodicAccrualProduct(12.0)
|
||||||
|
.name(Utils.uniqueRandomStringGenerator("UNDISBURSED_TEST_", 4))
|
||||||
|
.shortName(Utils.uniqueRandomStringGenerator("UT", 2));
|
||||||
|
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(loanProductRequest);
|
||||||
|
|
||||||
|
PostLoansResponse loanResponse = loanTransactionHelper
|
||||||
|
.applyLoan(applyLoanRequest(clientId, loanProduct.getResourceId(), "01 January 2024", 10000.0, 4));
|
||||||
|
Long loanId = loanResponse.getLoanId();
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(10000.0, "01 January 2024"));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||||
|
assertNotNull(loanDetails);
|
||||||
|
assertEquals("loanStatusType.approved", loanDetails.getStatus().getCode());
|
||||||
|
|
||||||
|
String transferExternalId = UUID.randomUUID().toString();
|
||||||
|
String ownerExternalId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
PostInitiateTransferResponse transferResponse = externalAssetOwnerHelper.initiateTransferByLoanId(loanId, "sale",
|
||||||
|
new ExternalAssetOwnerRequest().settlementDate("01 January 2024").dateFormat("dd MMMM yyyy").locale("en")
|
||||||
|
.transferExternalId(transferExternalId).ownerExternalId(ownerExternalId).purchasePriceRatio("1.0"));
|
||||||
|
|
||||||
|
assertNotNull(transferResponse);
|
||||||
|
assertEquals(transferExternalId, transferResponse.getResourceExternalId());
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanAfterTransfer = loanTransactionHelper.getLoanDetails(loanId);
|
||||||
|
assertNotNull(loanAfterTransfer, "Loan details should not be null");
|
||||||
|
|
||||||
|
if (loanAfterTransfer.getSummary() == null) {
|
||||||
|
assertEquals("loanStatusType.approved", loanAfterTransfer.getStatus().getCode(),
|
||||||
|
"Loan should remain in approved status");
|
||||||
|
|
||||||
|
assertEquals(0, BigDecimal.valueOf(10000.0).compareTo(loanAfterTransfer.getApprovedPrincipal()),
|
||||||
|
"Approved principal should be 10000");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail("Unexpected: Loan summary should be null for undisbursed loans");
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExternalAssetOwnerTransferForBackdatedUndisbursedLoan() {
|
||||||
|
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID, true);
|
||||||
|
|
||||||
|
Account transferAccount = accountHelper.createAssetAccount();
|
||||||
|
FinancialActivityAccountHelper financialActivityAccountHelper = new FinancialActivityAccountHelper(requestSpec);
|
||||||
|
ExternalAssetOwnerHelper externalAssetOwnerHelper = new ExternalAssetOwnerHelper();
|
||||||
|
externalAssetOwnerHelper.setProperFinancialActivity(financialActivityAccountHelper, transferAccount);
|
||||||
|
|
||||||
|
try {
|
||||||
|
runAt("01 March 2024", () -> {
|
||||||
|
PostClientsResponse client = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
Long clientId = client.getClientId();
|
||||||
|
|
||||||
|
PostLoanProductsRequest loanProductRequest = createOnePeriod30DaysPeriodicAccrualProduct(12.0)
|
||||||
|
.name(Utils.uniqueRandomStringGenerator("BACKDATED_UNDISBURSED_", 4))
|
||||||
|
.shortName(Utils.uniqueRandomStringGenerator("BU", 2));
|
||||||
|
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(loanProductRequest);
|
||||||
|
|
||||||
|
PostLoansResponse loanResponse = loanTransactionHelper
|
||||||
|
.applyLoan(applyLoanRequest(clientId, loanProduct.getResourceId(), "01 December 2023", 15000.0, 4));
|
||||||
|
Long loanId = loanResponse.getLoanId();
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(15000.0, "01 December 2023"));
|
||||||
|
|
||||||
|
String transferExternalId = UUID.randomUUID().toString();
|
||||||
|
String ownerExternalId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
PostInitiateTransferResponse transferResponse = externalAssetOwnerHelper.initiateTransferByLoanId(loanId, "sale",
|
||||||
|
new ExternalAssetOwnerRequest().settlementDate("01 March 2024").dateFormat("dd MMMM yyyy").locale("en")
|
||||||
|
.transferExternalId(transferExternalId).ownerExternalId(ownerExternalId).purchasePriceRatio("1.0"));
|
||||||
|
|
||||||
|
assertNotNull(transferResponse);
|
||||||
|
assertEquals(transferExternalId, transferResponse.getResourceExternalId());
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanAfterTransfer = loanTransactionHelper.getLoanDetails(loanId);
|
||||||
|
assertNotNull(loanAfterTransfer, "Loan details should not be null");
|
||||||
|
|
||||||
|
if (loanAfterTransfer.getSummary() == null) {
|
||||||
|
assertEquals("loanStatusType.approved", loanAfterTransfer.getStatus().getCode(),
|
||||||
|
"Loan should remain in approved status");
|
||||||
|
|
||||||
|
assertEquals(0, BigDecimal.valueOf(15000.0).compareTo(loanAfterTransfer.getApprovedPrincipal()),
|
||||||
|
"Approved principal should be 15000");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail("Unexpected: Loan summary should be null for undisbursed loans");
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExternalAssetOwnerTransferComparison_DisbursedVsUndisbursed() {
|
||||||
|
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID, true);
|
||||||
|
|
||||||
|
Account transferAccount = accountHelper.createAssetAccount();
|
||||||
|
FinancialActivityAccountHelper financialActivityAccountHelper = new FinancialActivityAccountHelper(requestSpec);
|
||||||
|
ExternalAssetOwnerHelper externalAssetOwnerHelper = new ExternalAssetOwnerHelper();
|
||||||
|
externalAssetOwnerHelper.setProperFinancialActivity(financialActivityAccountHelper, transferAccount);
|
||||||
|
|
||||||
|
try {
|
||||||
|
runAt("01 January 2024", () -> {
|
||||||
|
PostClientsResponse client1 = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
PostClientsResponse client2 = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
|
||||||
|
PostLoanProductsRequest loanProductRequest = createOnePeriod30DaysPeriodicAccrualProduct(12.0)
|
||||||
|
.name(Utils.uniqueRandomStringGenerator("COMPARISON_TEST_", 4))
|
||||||
|
.shortName(Utils.uniqueRandomStringGenerator("CT", 2));
|
||||||
|
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(loanProductRequest);
|
||||||
|
|
||||||
|
PostLoansResponse loan1Response = loanTransactionHelper
|
||||||
|
.applyLoan(applyLoanRequest(client1.getClientId(), loanProduct.getResourceId(), "01 January 2024", 20000.0, 4));
|
||||||
|
loan1Id = loan1Response.getLoanId();
|
||||||
|
|
||||||
|
PostLoansResponse loan2Response = loanTransactionHelper
|
||||||
|
.applyLoan(applyLoanRequest(client2.getClientId(), loanProduct.getResourceId(), "01 January 2024", 20000.0, 4));
|
||||||
|
loan2Id = loan2Response.getLoanId();
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loan1Id, approveLoanRequest(20000.0, "01 January 2024"));
|
||||||
|
loanTransactionHelper.approveLoan(loan2Id, approveLoanRequest(20000.0, "01 January 2024"));
|
||||||
|
|
||||||
|
disburseLoan(loan1Id, BigDecimal.valueOf(20000.0), "01 January 2024");
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("31 January 2024", () -> {
|
||||||
|
executeInlineCOB(loan1Id);
|
||||||
|
executeInlineCOB(loan2Id);
|
||||||
|
|
||||||
|
String transfer1ExternalId = UUID.randomUUID().toString();
|
||||||
|
String transfer2ExternalId = UUID.randomUUID().toString();
|
||||||
|
String ownerExternalId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
PostInitiateTransferResponse transfer1Response = externalAssetOwnerHelper.initiateTransferByLoanId(loan1Id, "sale",
|
||||||
|
new ExternalAssetOwnerRequest().settlementDate("31 January 2024").dateFormat("dd MMMM yyyy").locale("en")
|
||||||
|
.transferExternalId(transfer1ExternalId).ownerExternalId(ownerExternalId).purchasePriceRatio("1.0"));
|
||||||
|
|
||||||
|
PostInitiateTransferResponse transfer2Response = externalAssetOwnerHelper.initiateTransferByLoanId(loan2Id, "sale",
|
||||||
|
new ExternalAssetOwnerRequest().settlementDate("31 January 2024").dateFormat("dd MMMM yyyy").locale("en")
|
||||||
|
.transferExternalId(transfer2ExternalId).ownerExternalId(ownerExternalId).purchasePriceRatio("1.0"));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse disbursedLoan = loanTransactionHelper.getLoanDetails(loan1Id);
|
||||||
|
GetLoansLoanIdResponse undisbursedLoan = loanTransactionHelper.getLoanDetails(loan2Id);
|
||||||
|
|
||||||
|
assertNotNull(disbursedLoan, "Disbursed loan details should not be null");
|
||||||
|
assertNotNull(undisbursedLoan, "Undisbursed loan details should not be null");
|
||||||
|
|
||||||
|
assertNotNull(disbursedLoan.getSummary(), "Disbursed loan summary should not be null");
|
||||||
|
|
||||||
|
if (undisbursedLoan.getSummary() == null) {
|
||||||
|
assertEquals("loanStatusType.active", disbursedLoan.getStatus().getCode(), "Disbursed loan should be active");
|
||||||
|
assertEquals("loanStatusType.approved", undisbursedLoan.getStatus().getCode(),
|
||||||
|
"Undisbursed loan should remain approved");
|
||||||
|
|
||||||
|
BigDecimal disbursedInterest = disbursedLoan.getSummary().getInterestOutstanding();
|
||||||
|
assertNotNull(disbursedInterest, "Disbursed loan interest outstanding should not be null");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail("Unexpected: Undisbursed loan should not have summary data");
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -698,8 +698,8 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT
|
|||||||
new BigDecimal("757.420000"), new BigDecimal("0.000000"), new BigDecimal("0.000000"),
|
new BigDecimal("757.420000"), new BigDecimal("0.000000"), new BigDecimal("0.000000"),
|
||||||
new BigDecimal("0.000000")),
|
new BigDecimal("0.000000")),
|
||||||
ExpectedExternalTransferData.expected(BUYBACK, buybackTransferResponse.getResourceExternalId(), "2020-03-06",
|
ExpectedExternalTransferData.expected(BUYBACK, buybackTransferResponse.getResourceExternalId(), "2020-03-06",
|
||||||
"2020-03-05", "2020-03-05", true, new BigDecimal("15757.420000"), new BigDecimal("15000.000000"),
|
"2020-03-05", "2020-03-05", true, new BigDecimal("0.000000"), new BigDecimal("0.000000"),
|
||||||
new BigDecimal("757.420000"), new BigDecimal("0.000000"), new BigDecimal("0.000000"),
|
new BigDecimal("0.000000"), new BigDecimal("0.000000"), new BigDecimal("0.000000"),
|
||||||
new BigDecimal("0.000000")));
|
new BigDecimal("0.000000")));
|
||||||
getAndValidateThereIsNoActiveMapping(saleTransferResponse.getResourceExternalId());
|
getAndValidateThereIsNoActiveMapping(saleTransferResponse.getResourceExternalId());
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user