This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 0762a012e5 FINERACT-2181: Fix the interest calculation for non active
loan
0762a012e5 is described below
commit 0762a012e57cfca67bf68cb5d8840a85e60e0e7a
Author: Attila Budai <[email protected]>
AuthorDate: Wed Jul 16 13:39:01 2025 +0200
FINERACT-2181: Fix the interest calculation for non active loan
---
.../api/GlobalConfigurationConstants.java | 2 +
.../domain/ConfigurationDomainService.java | 5 +
...wnerTransferOutstandingInterestCalculation.java | 5 +
.../ExternalAssetOwnersWriteServiceImpl.java | 26 +-
.../module/investor/module-changelog-master.xml | 1 +
...0019_add_configurable_allowed_loan_statuses.xml | 47 ++++
...TransferOutstandingInterestCalculationTest.java | 127 ++++++++++
.../ExternalAssetOwnersWriteServiceTest.java | 13 +-
.../domain/ConfigurationDomainServiceJpa.java | 15 ++
.../LoanWritePlatformServiceJpaRepositoryImpl.java | 6 +-
.../common/GlobalConfigurationHelper.java | 23 +-
...ernalAssetOwnerTransferUndisbursedLoanTest.java | 264 +++++++++++++++++++++
.../InitiateExternalAssetOwnerTransferTest.java | 4 +-
13 files changed, 517 insertions(+), 21 deletions(-)
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java
index 3e6426a1d3..979eea042c 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java
@@ -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() {}
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
index 7a138fb25f..e7a98fb2cc 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
@@ -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();
diff --git
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculation.java
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculation.java
index eac4d0d9f2..5926ed6c82 100644
---
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculation.java
+++
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculation.java
@@ -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();
diff --git
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java
index 0ba4f709cd..19d4be902a 100644
---
a/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java
+++
b/fineract-investor/src/main/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceImpl.java
@@ -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());
+ }
}
diff --git
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
index e96557908c..5e955c664b 100644
---
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
+++
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/module-changelog-master.xml
@@ -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>
diff --git
a/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0019_add_configurable_allowed_loan_statuses.xml
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0019_add_configurable_allowed_loan_statuses.xml
new file mode 100644
index 0000000000..552f6a421c
--- /dev/null
+++
b/fineract-investor/src/main/resources/db/changelog/tenant/module/investor/parts/0019_add_configurable_allowed_loan_statuses.xml
@@ -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>
diff --git
a/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculationTest.java
b/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculationTest.java
new file mode 100644
index 0000000000..799d16c526
--- /dev/null
+++
b/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnerTransferOutstandingInterestCalculationTest.java
@@ -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();
+ }
+}
diff --git
a/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceTest.java
b/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceTest.java
index fef26dd754..c058214ba7 100644
---
a/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceTest.java
+++
b/fineract-investor/src/test/java/org/apache/fineract/investor/service/ExternalAssetOwnersWriteServiceTest.java
@@ -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"));
+
}
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
index cc17cad248..26b48b2f06 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -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();
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index dcd2a834cb..cd17636d54 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -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);
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
index c16780be26..4bbf06ed84 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java
@@ -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;
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTransferUndisbursedLoanTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTransferUndisbursedLoanTest.java
new file mode 100644
index 0000000000..83d5b30fde
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/ExternalAssetOwnerTransferUndisbursedLoanTest.java
@@ -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);
+ }
+ }
+}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
index 15e9aca31f..86720e42e8 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
@@ -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 {