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_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 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() {}
|
||||
}
|
||||
|
||||
+5
@@ -19,12 +19,17 @@
|
||||
package org.apache.fineract.infrastructure.configuration.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.cache.domain.CacheType;
|
||||
|
||||
public interface ConfigurationDomainService {
|
||||
|
||||
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
|
||||
|
||||
List<String> getAllowedLoanStatusesForExternalAssetTransfer();
|
||||
|
||||
List<String> getAllowedLoanStatusesOfDelayedSettlementForExternalAssetTransfer();
|
||||
|
||||
boolean isSameMakerCheckerEnabled();
|
||||
|
||||
boolean isAmazonS3Enabled();
|
||||
|
||||
+5
@@ -49,6 +49,11 @@ public class ExternalAssetOwnerTransferOutstandingInterestCalculation {
|
||||
}
|
||||
|
||||
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();
|
||||
return switch (outstandingInterestCalculationStrategy) {
|
||||
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.PENDING;
|
||||
import static org.apache.fineract.investor.data.ExternalTransferStatus.PENDING_INTERMEDIATE;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.ACTIVE;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.CLOSED_OBLIGATIONS_MET;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.OVERPAID;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.TRANSFER_IN_PROGRESS;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanStatus.TRANSFER_ON_HOLD;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
@@ -43,6 +38,7 @@ import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
@@ -74,9 +70,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersWriteService {
|
||||
|
||||
private static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||
private static final List<LoanStatus> ACTIVE_LOAN_STATUSES = List.of(ACTIVE, TRANSFER_IN_PROGRESS, TRANSFER_ON_HOLD);
|
||||
private static final List<LoanStatus> VALID_DELAYED_SETTLEMENT_LOAN_STATUSES_BUYBACK_AND_SALE = List.of(ACTIVE, TRANSFER_IN_PROGRESS,
|
||||
TRANSFER_ON_HOLD, OVERPAID, CLOSED_OBLIGATIONS_MET);
|
||||
private static final List<ExternalTransferStatus> BUYBACK_READY_STATUSES = List.of(ExternalTransferStatus.PENDING,
|
||||
ExternalTransferStatus.ACTIVE);
|
||||
private static final List<ExternalTransferStatus> BUYBACK_READY_STATUSES_FOR_DELAY_SETTLEMENT = List
|
||||
@@ -86,6 +79,7 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanRepository loanRepository;
|
||||
private final DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -388,16 +382,16 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
|
||||
private void validateLoanStatusIntermediarySale(LoanDataForExternalTransfer loanDataForExternalTransfer) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
private List<LoanStatus> getValidLoanStatusList(boolean isDelayedSettlementEnabled) {
|
||||
if (isDelayedSettlementEnabled) {
|
||||
return VALID_DELAYED_SETTLEMENT_LOAN_STATUSES_BUYBACK_AND_SALE;
|
||||
return getAllowedLoanStatusesForDelayedSettlement();
|
||||
} else {
|
||||
return ACTIVE_LOAN_STATUSES;
|
||||
return getAllowedLoanStatuses();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,4 +571,14 @@ public class ExternalAssetOwnersWriteServiceImpl implements ExternalAssetOwnersW
|
||||
externalAssetOwner.setExternalId(ExternalIdFactory.produce(externalId));
|
||||
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/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/0019_add_configurable_allowed_loan_statuses.xml"/>
|
||||
</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 org.apache.commons.lang3.RandomStringUtils;
|
||||
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.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
@@ -418,8 +419,8 @@ public class ExternalAssetOwnersWriteServiceTest {
|
||||
}
|
||||
|
||||
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),
|
||||
return Stream.of(Arguments.of("Invalid Loan Status", LoanStatus.INVALID), 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("Withdrawn By Client Loan Status", LoanStatus.WITHDRAWN_BY_CLIENT),
|
||||
Arguments.of("Closed Written Off Loan Status", LoanStatus.CLOSED_WRITTEN_OFF),
|
||||
@@ -860,6 +861,9 @@ public class ExternalAssetOwnersWriteServiceTest {
|
||||
@Mock
|
||||
private LoanDataForExternalTransfer loanDataForExternalTransfer;
|
||||
|
||||
@Mock
|
||||
private ConfigurationDomainService configurationDomainService;
|
||||
|
||||
@InjectMocks
|
||||
private ExternalAssetOwnersWriteServiceImpl externalAssetOwnersWriteServiceImpl;
|
||||
|
||||
@@ -926,6 +930,11 @@ public class ExternalAssetOwnersWriteServiceTest {
|
||||
lenient().when(loanDataForExternalTransfer.getLoanStatus()).thenReturn(LoanStatus.ACTIVE);
|
||||
lenient().when(loanDataForExternalTransfer.getLoanProductId()).thenReturn(loanProductId);
|
||||
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 java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -60,6 +61,20 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
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
|
||||
public boolean isSameMakerCheckerEnabled() {
|
||||
return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.ENABLE_SAME_MAKER_CHECKER).isEnabled();
|
||||
|
||||
+2
-4
@@ -3315,9 +3315,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan);
|
||||
changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus()));
|
||||
|
||||
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
|
||||
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
|
||||
|
||||
@@ -3359,7 +3356,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||
|
||||
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||
|
||||
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan);
|
||||
changes.put(PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus()));
|
||||
return Optional.of(loanTransaction);
|
||||
}
|
||||
|
||||
|
||||
+21
-2
@@ -105,8 +105,8 @@ public class GlobalConfigurationHelper {
|
||||
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
|
||||
GetGlobalConfigurationsResponse actualGlobalConfigurations = getAllGlobalConfigurations();
|
||||
|
||||
Assertions.assertEquals(57, expectedGlobalConfigurations.size());
|
||||
Assertions.assertEquals(57, actualGlobalConfigurations.getGlobalConfiguration().size());
|
||||
Assertions.assertEquals(59, expectedGlobalConfigurations.size());
|
||||
Assertions.assertEquals(59, actualGlobalConfigurations.getGlobalConfiguration().size());
|
||||
|
||||
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
|
||||
|
||||
@@ -549,6 +549,25 @@ public class GlobalConfigurationHelper {
|
||||
assetOwnerTransferInterestOutstandingStrategy.put("string_value", "TOTAL_OUTSTANDING_INTEREST");
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+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("0.000000")),
|
||||
ExpectedExternalTransferData.expected(BUYBACK, buybackTransferResponse.getResourceExternalId(), "2020-03-06",
|
||||
"2020-03-05", "2020-03-05", true, new BigDecimal("15757.420000"), new BigDecimal("15000.000000"),
|
||||
new BigDecimal("757.420000"), new BigDecimal("0.000000"), new BigDecimal("0.000000"),
|
||||
"2020-03-05", "2020-03-05", true, 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());
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user