FINERACT-2286: Make configurable the outstanding interest strategy during asset owner transfer
This commit is contained in:
+1
@@ -76,6 +76,7 @@ public final class GlobalConfigurationConstants {
|
||||
public static final String NEXT_PAYMENT_DUE_DATE = "next-payment-due-date";
|
||||
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";
|
||||
|
||||
private GlobalConfigurationConstants() {}
|
||||
}
|
||||
|
||||
+1
@@ -145,4 +145,5 @@ public interface ConfigurationDomainService {
|
||||
|
||||
boolean isImmediateChargeAccrualPostMaturityEnabled();
|
||||
|
||||
String getAssetOwnerTransferOustandingInterestStrategy();
|
||||
}
|
||||
|
||||
+6
@@ -74,4 +74,10 @@ public class GlobalConfigurationHelper {
|
||||
boolean isEnabled = BooleanUtils.toBoolean(updatedConfiguration.body().getEnabled());
|
||||
assertThat(isEnabled).isEqualTo(true);
|
||||
}
|
||||
|
||||
public GlobalConfigurationPropertyData getGlobalConfiguration(String configKey) throws IOException {
|
||||
Response<GlobalConfigurationPropertyData> configuration = globalConfigurationApi.retrieveOneByName(configKey).execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(configuration);
|
||||
return configuration.body();
|
||||
}
|
||||
}
|
||||
|
||||
+16
-5
@@ -41,6 +41,7 @@ import org.apache.fineract.client.models.GetClientsClientIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdDelinquencyPausePeriod;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
||||
import org.apache.fineract.client.models.GlobalConfigurationPropertyData;
|
||||
import org.apache.fineract.client.models.PageExternalTransferData;
|
||||
import org.apache.fineract.client.models.PostClientsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||
@@ -53,6 +54,7 @@ import org.apache.fineract.test.data.AssetExternalizationTransferStatus;
|
||||
import org.apache.fineract.test.data.AssetExternalizationTransferStatusReason;
|
||||
import org.apache.fineract.test.data.TransactionType;
|
||||
import org.apache.fineract.test.helper.ErrorMessageHelper;
|
||||
import org.apache.fineract.test.helper.GlobalConfigurationHelper;
|
||||
import org.apache.fineract.test.messaging.EventAssertion;
|
||||
import org.apache.fineract.test.messaging.event.assetexternalization.LoanAccountSnapshotEvent;
|
||||
import org.apache.fineract.test.messaging.event.assetexternalization.LoanOwnershipTransferEvent;
|
||||
@@ -90,15 +92,14 @@ public class EventCheckHelper {
|
||||
|
||||
@Autowired
|
||||
private ClientApi clientApi;
|
||||
|
||||
@Autowired
|
||||
private LoansApi loansApi;
|
||||
|
||||
@Autowired
|
||||
private EventAssertion eventAssertion;
|
||||
|
||||
@Autowired
|
||||
private ExternalAssetOwnersApi externalAssetOwnersApi;
|
||||
@Autowired
|
||||
private GlobalConfigurationHelper configurationHelper;
|
||||
|
||||
public void clientEventCheck(Response<PostClientsResponse> clientCreationResponse) throws IOException {
|
||||
Response<GetClientsClientIdResponse> clientDetails = clientApi.retrieveOne11(clientCreationResponse.body().getClientId(), false)
|
||||
@@ -412,14 +413,24 @@ public class EventCheckHelper {
|
||||
ExternalTransferData filtered = content.stream().filter(t -> transferId.equals(t.getTransferId())).reduce((first, second) -> second)
|
||||
.orElseThrow(() -> new IllegalStateException("No element found"));
|
||||
|
||||
BigDecimal totalOutstandingBalanceAmountExpected = zeroConversion(filtered.getDetails().getTotalOutstanding());
|
||||
BigDecimal outstandingInterestPortionExpected = zeroConversion(filtered.getDetails().getTotalInterestOutstanding());
|
||||
|
||||
GlobalConfigurationPropertyData outstandingInterestStrategy = configurationHelper
|
||||
.getGlobalConfiguration("outstanding-interest-calculation-strategy-for-external-asset-transfer");
|
||||
if ("PAYABLE_OUTSTANDING_INTEREST".equals(outstandingInterestStrategy.getStringValue())) {
|
||||
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "all", null, null).execute();
|
||||
totalOutstandingBalanceAmountExpected = BigDecimal.valueOf(loanDetails.body().getSummary().getTotalOutstanding());
|
||||
outstandingInterestPortionExpected = BigDecimal.valueOf(loanDetails.body().getSummary().getInterestOutstanding());
|
||||
}
|
||||
|
||||
String ownerExternalIdExpected = filtered.getStatus().getValue().equals("BUYBACK") ? null : filtered.getOwner().getExternalId();
|
||||
String settlementDateExpected = filtered.getStatus().getValue().equals("BUYBACK") ? null
|
||||
: FORMATTER_EVENTS.format(filtered.getSettlementDate());
|
||||
BigDecimal totalOutstandingBalanceAmountExpected = zeroConversion(filtered.getDetails().getTotalOutstanding());
|
||||
BigDecimal outstandingPrincipalPortionExpected = zeroConversion(filtered.getDetails().getTotalPrincipalOutstanding());
|
||||
BigDecimal outstandingFeePortionExpected = zeroConversion(filtered.getDetails().getTotalFeeChargesOutstanding());
|
||||
BigDecimal outstandingPenaltyPortionExpected = zeroConversion(filtered.getDetails().getTotalPenaltyChargesOutstanding());
|
||||
BigDecimal outstandingInterestPortionExpected = zeroConversion(filtered.getDetails().getTotalInterestOutstanding());
|
||||
|
||||
BigDecimal overPaymentPortionExpected = zeroConversion(filtered.getDetails().getTotalOverpaid());
|
||||
|
||||
eventAssertion.assertEvent(LoanAccountSnapshotEvent.class, loanId).extractingData(LoanAccountDataV1::getId).isEqualTo(loanId)
|
||||
|
||||
+30
-5
@@ -341,11 +341,6 @@ public class AssetExternalizationStepDef extends AbstractStepDef {
|
||||
transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE);
|
||||
}
|
||||
|
||||
expectedValues.add(ownerExternalIdStored);
|
||||
expectedValues.add(loanId == null ? null : String.valueOf(loanId));
|
||||
expectedValues.add(loanExternalId);
|
||||
expectedValues.add(transferExternalId);
|
||||
|
||||
List<List<String>> actualValuesList = content.stream().map(t -> {
|
||||
List<String> actualValues = new ArrayList<>();
|
||||
actualValues.add(t.getSettlementDate() == null ? null : FORMATTER.format(t.getSettlementDate()));
|
||||
@@ -354,6 +349,31 @@ public class AssetExternalizationStepDef extends AbstractStepDef {
|
||||
actualValues.add(t.getEffectiveFrom() == null ? null : FORMATTER.format(t.getEffectiveFrom()));
|
||||
actualValues.add(t.getEffectiveTo() == null ? null : FORMATTER.format(t.getEffectiveTo()));
|
||||
actualValues.add(transactionType);
|
||||
if (expectedValues.size() > 6) {
|
||||
actualValues.add(
|
||||
t.getDetails() != null ? t.getDetails().getTotalOutstanding().setScale(2, RoundingMode.HALF_DOWN).toString()
|
||||
: null);
|
||||
}
|
||||
if (expectedValues.size() > 7) {
|
||||
actualValues.add(t.getDetails() != null
|
||||
? t.getDetails().getTotalPrincipalOutstanding().setScale(2, RoundingMode.HALF_DOWN).toString()
|
||||
: null);
|
||||
}
|
||||
if (expectedValues.size() > 8) {
|
||||
actualValues.add(t.getDetails() != null
|
||||
? t.getDetails().getTotalInterestOutstanding().setScale(2, RoundingMode.HALF_DOWN).toString()
|
||||
: null);
|
||||
}
|
||||
if (expectedValues.size() > 9) {
|
||||
actualValues.add(t.getDetails() != null
|
||||
? t.getDetails().getTotalFeeChargesOutstanding().setScale(2, RoundingMode.HALF_DOWN).toString()
|
||||
: null);
|
||||
}
|
||||
if (expectedValues.size() > 10) {
|
||||
actualValues.add(t.getDetails() != null
|
||||
? t.getDetails().getTotalPenaltyChargesOutstanding().setScale(2, RoundingMode.HALF_DOWN).toString()
|
||||
: null);
|
||||
}
|
||||
actualValues.add(t.getOwner().getExternalId() == null ? null : t.getOwner().getExternalId());
|
||||
actualValues.add(loanId == null ? null : String.valueOf(t.getLoan().getLoanId()));
|
||||
actualValues.add(loanExternalId == null ? null : t.getLoan().getExternalId());
|
||||
@@ -361,6 +381,11 @@ public class AssetExternalizationStepDef extends AbstractStepDef {
|
||||
return actualValues;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
expectedValues.add(ownerExternalIdStored);
|
||||
expectedValues.add(loanId == null ? null : String.valueOf(loanId));
|
||||
expectedValues.add(loanExternalId);
|
||||
expectedValues.add(transferExternalId);
|
||||
|
||||
boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
|
||||
|
||||
assertThat(numberOfElementsActual)
|
||||
|
||||
@@ -1679,3 +1679,100 @@ Feature: Asset Externalization
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
|
||||
@TestRailId:C3690 @AssetExternalizationJournalEntry
|
||||
Scenario: Verify that Asset externalization SALES and BUYBACK has the correct Journal entries with PAYABLE_OUTSTANDING_INTEREST strategy
|
||||
When Global config "outstanding-interest-calculation-strategy-for-external-asset-transfer" value set to "PAYABLE_OUTSTANDING_INTEREST"
|
||||
When Admin sets the business date to "1 May 2023"
|
||||
When Admin creates a client with random data
|
||||
When Admin creates a fully customized loan with the following data:
|
||||
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||
| LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL | 01 May 2023 | 1000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||
And Admin successfully approves the loan on "1 May 2023" with "1000" amount and expected disbursement date on "1 May 2023"
|
||||
When Admin successfully disburse the loan on "1 May 2023" with "1000" EUR transaction amount
|
||||
Then Loan status will be "ACTIVE"
|
||||
When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data:
|
||||
| Transaction type | settlementDate | purchasePriceRatio |
|
||||
| sale | 2023-05-21 | 1 |
|
||||
Then Asset externalization response has the correct Loan ID, transferExternalId
|
||||
Then Fetching Asset externalization details by loan id gives numberOfElements: 1 with correct ownerExternalId and the following data:
|
||||
| settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | totalOutstanding | totalPrincipalOutstanding | totalInterestOutstanding | totalFeeChargesOutstanding | totalPenaltyChargesOutstanding |
|
||||
| 2023-05-21 | 1 | PENDING | 2023-05-01 | 9999-12-31 | SALE | | | | | |
|
||||
When Admin adds "LOAN_SNOOZE_FEE" due date charge with "10 May 2023" due date and 10 EUR transaction amount
|
||||
When Admin sets the business date to "10 May 2023"
|
||||
When Admin runs inline COB job for Loan
|
||||
When Admin sets the business date to "22 May 2023"
|
||||
When Admin runs inline COB job for Loan
|
||||
Then LoanOwnershipTransferBusinessEvent is created
|
||||
Then LoanAccountSnapshotBusinessEvent is created
|
||||
Then Fetching Asset externalization details by loan id gives numberOfElements: 2 with correct ownerExternalId and the following data:
|
||||
| settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | totalOutstanding | totalPrincipalOutstanding | totalInterestOutstanding | totalFeeChargesOutstanding | totalPenaltyChargesOutstanding |
|
||||
| 2023-05-21 | 1 | PENDING | 2023-05-01 | 2023-05-21 | SALE | | | | | |
|
||||
| 2023-05-21 | 1 | ACTIVE | 2023-05-22 | 9999-12-31 | SALE | 1016.58 | 1000.00 | 6.58 | 10.00 | 0.00 |
|
||||
Then The latest asset externalization transaction with "ACTIVE" status has the following TRANSFER Journal entries:
|
||||
| glAccountType | glAccountCode | glAccountName | entryType | amount |
|
||||
| ASSET | 112601 | Loans Receivable | CREDIT | 1000.00 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | CREDIT | 16.58 |
|
||||
| ASSET | 146000 | Asset transfer | DEBIT | 1016.58 |
|
||||
| ASSET | 112601 | Loans Receivable | DEBIT | 1000.00 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 16.58 |
|
||||
| ASSET | 146000 | Asset transfer | CREDIT | 1016.58 |
|
||||
Then The asset external owner has the following OWNER Journal entries:
|
||||
| glAccountType | glAccountCode | glAccountName | entryType | amount |
|
||||
| ASSET | 112601 | Loans Receivable | DEBIT | 1000.00 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 16.58 |
|
||||
When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data:
|
||||
| Transaction type | settlementDate | purchasePriceRatio |
|
||||
| buyback | 2023-05-30 | |
|
||||
Then Fetching Asset externalization details by loan id gives numberOfElements: 3 with correct ownerExternalId and the following data:
|
||||
| settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | totalOutstanding | totalPrincipalOutstanding | totalInterestOutstanding | totalFeeChargesOutstanding | totalPenaltyChargesOutstanding |
|
||||
| 2023-05-21 | 1 | PENDING | 2023-05-01 | 2023-05-21 | SALE | | | | | |
|
||||
| 2023-05-21 | 1 | ACTIVE | 2023-05-22 | 9999-12-31 | SALE | 1016.58 | 1000.00 | 6.58 | 10.00 | 0.00 |
|
||||
| 2023-05-30 | 1 | BUYBACK | 2023-05-22 | 9999-12-31 | BUYBACK | | | | | |
|
||||
When Admin adds "LOAN_NSF_FEE" due date charge with "25 May 2023" due date and 20 EUR transaction amount
|
||||
When Admin sets the business date to "26 May 2023"
|
||||
When Admin runs inline COB job for Loan
|
||||
When Admin sets the business date to "31 May 2023"
|
||||
When Admin runs inline COB job for Loan
|
||||
Then LoanOwnershipTransferBusinessEvent is created
|
||||
Then LoanAccountSnapshotBusinessEvent is created
|
||||
Then Fetching Asset externalization details by loan id gives numberOfElements: 3 with correct ownerExternalId and the following data:
|
||||
| settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | totalOutstanding | totalPrincipalOutstanding | totalInterestOutstanding | totalFeeChargesOutstanding | totalPenaltyChargesOutstanding |
|
||||
| 2023-05-21 | 1 | PENDING | 2023-05-01 | 2023-05-21 | SALE | | | | | |
|
||||
| 2023-05-21 | 1 | ACTIVE | 2023-05-22 | 2023-05-30 | SALE | 1016.58 | 1000.00 | 6.58 | 10.00 | 0.00 |
|
||||
| 2023-05-30 | 1 | BUYBACK | 2023-05-22 | 2023-05-30 | BUYBACK | 1039.53 | 1000.00 | 9.53 | 10.00 | 20.00 |
|
||||
Then The latest asset externalization transaction with "BUYBACK" status has the following TRANSFER Journal entries:
|
||||
| glAccountType | glAccountCode | glAccountName | entryType | amount |
|
||||
| ASSET | 112601 | Loans Receivable | DEBIT | 1000.00 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 39.53 |
|
||||
| ASSET | 146000 | Asset transfer | CREDIT | 1039.53 |
|
||||
| ASSET | 112601 | Loans Receivable | CREDIT | 1000.00 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | CREDIT | 39.53 |
|
||||
| ASSET | 146000 | Asset transfer | DEBIT | 1039.53 |
|
||||
Then The asset external owner has the following OWNER Journal entries:
|
||||
| glAccountType | glAccountCode | glAccountName | entryType | amount |
|
||||
| ASSET | 112601 | Loans Receivable | DEBIT | 1000.00 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 16.58 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | DEBIT | 0.33 |
|
||||
| INCOME | 404000 | Interest Income | CREDIT | 0.33 |
|
||||
When Global config "outstanding-interest-calculation-strategy-for-external-asset-transfer" value set to "TOTAL_OUTSTANDING_INTEREST"
|
||||
|
||||
+9
-9
@@ -21,7 +21,6 @@ package org.apache.fineract.investor.cob.loan;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -42,6 +41,7 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
|
||||
import org.apache.fineract.investor.domain.LoanOwnershipTransferBusinessEvent;
|
||||
import org.apache.fineract.investor.service.AccountingService;
|
||||
import org.apache.fineract.investor.service.DelayedSettlementAttributeService;
|
||||
import org.apache.fineract.investor.service.ExternalAssetOwnerTransferOutstandingInterestCalculation;
|
||||
import org.apache.fineract.investor.service.LoanTransferabilityService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
@@ -65,6 +65,7 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanTransferabilityService loanTransferabilityService;
|
||||
private final DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||
private final ExternalAssetOwnerTransferOutstandingInterestCalculation externalAssetOwnerTransferOutstandingInterestCalculation;
|
||||
|
||||
@Override
|
||||
public Loan execute(Loan loan) {
|
||||
@@ -220,14 +221,13 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransferDetails details = new ExternalAssetOwnerTransferDetails();
|
||||
details.setExternalAssetOwnerTransfer(externalAssetOwnerTransfer);
|
||||
details.setTotalOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPrincipalOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalPrincipalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalInterestOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalInterestOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalFeeChargesOutstanding(
|
||||
Objects.requireNonNullElse(loan.getSummary().getTotalFeeChargesOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPenaltyChargesOutstanding(
|
||||
Objects.requireNonNullElse(loan.getSummary().getTotalPenaltyChargesOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalOverpaid(Objects.requireNonNullElse(loan.getTotalOverpaid(), BigDecimal.ZERO));
|
||||
details.setTotalPrincipalOutstanding(loan.getSummary().getTotalPrincipalOutstanding());
|
||||
// We have different strategies to calculate oustanding interest
|
||||
final BigDecimal interestAmount = externalAssetOwnerTransferOutstandingInterestCalculation.calculateOutstandingInterest(loan);
|
||||
details.setTotalInterestOutstanding(interestAmount);
|
||||
details.setTotalFeeChargesOutstanding(loan.getSummary().getTotalFeeChargesOutstanding());
|
||||
details.setTotalPenaltyChargesOutstanding(loan.getSummary().getTotalPenaltyChargesOutstanding());
|
||||
details.setTotalOverpaid(loan.getTotalOverpaid());
|
||||
return details;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
public interface AttributeKey {
|
||||
|
||||
String getKey();
|
||||
|
||||
String getValue();
|
||||
|
||||
String getDescription();
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class ExternalAssetOwnerLoanProductAttribute implements AttributeKey {
|
||||
|
||||
public static final ExternalAssetOwnerLoanProductAttribute TOTAL_OUTSTANDING_INTEREST_STRATEGY = new ExternalAssetOwnerLoanProductAttribute(
|
||||
"OUTSTANDING_INTEREST_STRATEGY", "TOTAL_OUTSTANDING",
|
||||
"During external owner transfer the total (due + not yet due + projected) interest participate");
|
||||
public static final ExternalAssetOwnerLoanProductAttribute PAYABLE_OUTSTANDING_INTEREST_STRATEGY = new ExternalAssetOwnerLoanProductAttribute(
|
||||
"OUTSTANDING_INTEREST_STRATEGY", "PAYABLE_OUTSTANDING",
|
||||
"During external owner transfer the total (due + not yet due) interest participate");
|
||||
|
||||
private final String key;
|
||||
private final String value;
|
||||
private final String description;
|
||||
}
|
||||
+32
-1
@@ -25,18 +25,20 @@ import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Objects;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Table(name = "m_external_asset_owner_transfer_details")
|
||||
@NoArgsConstructor
|
||||
@Entity
|
||||
public class ExternalAssetOwnerTransferDetails extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
@Setter
|
||||
@OneToOne(cascade = CascadeType.ALL)
|
||||
@JoinColumn(name = "asset_owner_transfer_id", referencedColumnName = "id")
|
||||
private ExternalAssetOwnerTransfer externalAssetOwnerTransfer;
|
||||
@@ -58,4 +60,33 @@ public class ExternalAssetOwnerTransferDetails extends AbstractAuditableWithUTCD
|
||||
|
||||
@Column(name = "total_overpaid_derived", scale = 6, precision = 19, nullable = false)
|
||||
private BigDecimal totalOverpaid;
|
||||
|
||||
public void setTotalPrincipalOutstanding(BigDecimal totalPrincipalOutstanding) {
|
||||
this.totalPrincipalOutstanding = Objects.requireNonNullElse(totalPrincipalOutstanding, BigDecimal.ZERO);
|
||||
updateTotalOutstanding();
|
||||
}
|
||||
|
||||
public void setTotalInterestOutstanding(BigDecimal totalInterestOutstanding) {
|
||||
this.totalInterestOutstanding = Objects.requireNonNullElse(totalInterestOutstanding, BigDecimal.ZERO);
|
||||
updateTotalOutstanding();
|
||||
}
|
||||
|
||||
public void setTotalFeeChargesOutstanding(BigDecimal totalFeeChargesOutstanding) {
|
||||
this.totalFeeChargesOutstanding = Objects.requireNonNullElse(totalFeeChargesOutstanding, BigDecimal.ZERO);
|
||||
updateTotalOutstanding();
|
||||
}
|
||||
|
||||
public void setTotalPenaltyChargesOutstanding(BigDecimal totalPenaltyChargesOutstanding) {
|
||||
this.totalPenaltyChargesOutstanding = Objects.requireNonNullElse(totalPenaltyChargesOutstanding, BigDecimal.ZERO);
|
||||
updateTotalOutstanding();
|
||||
}
|
||||
|
||||
private void updateTotalOutstanding() {
|
||||
this.totalOutstanding = MathUtil.add(getTotalPrincipalOutstanding(), getTotalInterestOutstanding(), getTotalFeeChargesOutstanding(),
|
||||
getTotalPenaltyChargesOutstanding());
|
||||
}
|
||||
|
||||
public void setTotalOverpaid(BigDecimal totalOverpaid) {
|
||||
this.totalOverpaid = Objects.requireNonNullElse(totalOverpaid, BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -55,6 +55,7 @@ public class AccountingServiceImpl implements AccountingService {
|
||||
private final ExternalAssetOwnerTransferJournalEntryMappingRepository externalAssetOwnerTransferJournalEntryMappingRepository;
|
||||
private final ExternalAssetOwnerJournalEntryMappingRepository externalAssetOwnerJournalEntryMappingRepository;
|
||||
private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository;
|
||||
private final ExternalAssetOwnerTransferOutstandingInterestCalculation externalAssetOwnerTransferOutstandingInterestCalculation;
|
||||
|
||||
@Override
|
||||
public void createJournalEntriesForSaleAssetTransfer(final Loan loan, final ExternalAssetOwnerTransfer transfer,
|
||||
@@ -104,7 +105,8 @@ public class AccountingServiceImpl implements AccountingService {
|
||||
final Long transactionId = transfer.getId();
|
||||
final LocalDate transactionDate = transfer.getSettlementDate();
|
||||
final BigDecimal principalAmount = loan.getSummary().getTotalPrincipalOutstanding();
|
||||
final BigDecimal interestAmount = loan.getSummary().getTotalInterestOutstanding();
|
||||
// We have different strategies to calculate oustanding interest
|
||||
final BigDecimal interestAmount = externalAssetOwnerTransferOutstandingInterestCalculation.calculateOutstandingInterest(loan);
|
||||
final BigDecimal feesAmount = loan.getSummary().getTotalFeeChargesOutstanding();
|
||||
final BigDecimal penaltiesAmount = loan.getSummary().getTotalPenaltyChargesOutstanding();
|
||||
final BigDecimal overPaymentAmount = loan.getTotalOverpaid();
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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 java.math.BigDecimal;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.investor.config.InvestorModuleIsEnabledCondition;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
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.springframework.context.annotation.Conditional;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Conditional(InvestorModuleIsEnabledCondition.class)
|
||||
public class ExternalAssetOwnerTransferOutstandingInterestCalculation {
|
||||
|
||||
private final LoanSummaryProviderDelegate loanSummaryDataProvider;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
|
||||
private LoanSummaryDataProvider fetchLoanSummaryDataProvider(Loan loan) {
|
||||
return this.loanSummaryDataProvider.resolveLoanSummaryDataProvider(loan.getTransactionProcessingStrategyCode());
|
||||
}
|
||||
|
||||
public BigDecimal calculateOutstandingInterest(Loan loan) {
|
||||
String outstandingInterestCalculationStrategy = configurationDomainService.getAssetOwnerTransferOustandingInterestStrategy();
|
||||
return switch (outstandingInterestCalculationStrategy) {
|
||||
case "TOTAL_OUTSTANDING_INTEREST" -> loan.getSummary().getTotalInterestOutstanding();
|
||||
case "PAYABLE_OUTSTANDING_INTEREST" -> {
|
||||
LoanAccountData data = loanReadPlatformService.retrieveOne(loan.getId());
|
||||
data = loanReadPlatformService.fetchRepaymentScheduleData(data);
|
||||
Money duePayableAmount = loan
|
||||
.getRepaymentScheduleInstallments(i -> !i.getDueDate().isAfter(DateUtils.getBusinessLocalDate())).stream()
|
||||
.map(i -> i.getInterestOutstanding(loan.getCurrency())).reduce(Money.zero(loan.getCurrency()), MathUtil::plus);
|
||||
BigDecimal notDuePayableAmount = fetchLoanSummaryDataProvider(loan)
|
||||
.computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(loan, data.getRepaymentSchedule().getPeriods(),
|
||||
DateUtils.getBusinessLocalDate(), loan.getCurrency().toData(), duePayableAmount.getAmount());
|
||||
|
||||
yield MathUtil.add(duePayableAmount.getAmount(), notDuePayableAmount);
|
||||
}
|
||||
default -> throw new UnsupportedOperationException(
|
||||
"Unknown outstanding interest calculation: " + outstandingInterestCalculationStrategy);
|
||||
};
|
||||
}
|
||||
}
|
||||
+8
-9
@@ -35,7 +35,6 @@ import static org.apache.fineract.investor.data.ExternalTransferSubStatus.UNSOLD
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
@@ -63,6 +62,7 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
|
||||
private final ExternalAssetOwnerTransferLoanMappingRepository externalAssetOwnerTransferLoanMappingRepository;
|
||||
private final AccountingService accountingService;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final ExternalAssetOwnerTransferOutstandingInterestCalculation externalAssetOwnerTransferOutstandingInterestCalculation;
|
||||
|
||||
@Override
|
||||
public void handleLoanClosedOrOverpaid(Loan loan) {
|
||||
@@ -166,14 +166,13 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransferDetails details = new ExternalAssetOwnerTransferDetails();
|
||||
details.setExternalAssetOwnerTransfer(externalAssetOwnerTransfer);
|
||||
details.setTotalOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPrincipalOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalPrincipalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalInterestOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalInterestOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalFeeChargesOutstanding(
|
||||
Objects.requireNonNullElse(loan.getSummary().getTotalFeeChargesOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPenaltyChargesOutstanding(
|
||||
Objects.requireNonNullElse(loan.getSummary().getTotalPenaltyChargesOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalOverpaid(Objects.requireNonNullElse(loan.getTotalOverpaid(), BigDecimal.ZERO));
|
||||
details.setTotalPrincipalOutstanding(loan.getSummary().getTotalPrincipalOutstanding());
|
||||
// We have different strategies to calculate oustanding interest
|
||||
final BigDecimal interestAmount = externalAssetOwnerTransferOutstandingInterestCalculation.calculateOutstandingInterest(loan);
|
||||
details.setTotalInterestOutstanding(interestAmount);
|
||||
details.setTotalFeeChargesOutstanding(loan.getSummary().getTotalFeeChargesOutstanding());
|
||||
details.setTotalPenaltyChargesOutstanding(loan.getSummary().getTotalPenaltyChargesOutstanding());
|
||||
details.setTotalOverpaid(loan.getTotalOverpaid());
|
||||
return details;
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -39,4 +39,5 @@
|
||||
<include relativeToChangelogFile="true" file="parts/0015_add_intermediary_sale_command.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/0018_add_external_asset_owner_transfer_outstanding_interest_strategy.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?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" context="postgresql">
|
||||
<sql>
|
||||
SELECT SETVAL('c_configuration_id_seq', COALESCE(MAX(id), 0)+1, false ) FROM c_configuration;
|
||||
</sql>
|
||||
</changeSet>
|
||||
<changeSet author="fineract" id="2">
|
||||
<insert tableName="c_configuration">
|
||||
<column name="name" value="outstanding-interest-calculation-strategy-for-external-asset-transfer"/>
|
||||
<column name="value"/>
|
||||
<column name="date_value"/>
|
||||
<column name="string_value" value="TOTAL_OUTSTANDING_INTEREST"/>
|
||||
<column name="enabled" valueBoolean="true"/>
|
||||
<column name="is_trap_door" valueBoolean="false"/>
|
||||
<column name="description" value=" Available options: TOTAL_OUTSTANDING_INTEREST, PAYABLE_OUTSTANDING_INTEREST. Define whether total outstanding interest (due + not yet due + projected) or payable till transfer date ( due + not yet due) should be involved in the asset transfer."/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+24
-1
@@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -31,6 +32,8 @@ import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
@@ -55,12 +58,16 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
|
||||
import org.apache.fineract.investor.domain.LoanOwnershipTransferBusinessEvent;
|
||||
import org.apache.fineract.investor.service.AccountingService;
|
||||
import org.apache.fineract.investor.service.DelayedSettlementAttributeService;
|
||||
import org.apache.fineract.investor.service.ExternalAssetOwnerTransferOutstandingInterestCalculation;
|
||||
import org.apache.fineract.investor.service.LoanTransferabilityService;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -69,6 +76,7 @@ import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -80,6 +88,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
public static final LocalDate FUTURE_DATE_9999_12_31 = LocalDate.of(9999, 12, 31);
|
||||
private static final Long LOAN_PRODUCT_ID = 2L;
|
||||
private final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault());
|
||||
private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
@@ -99,8 +108,22 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
@Mock
|
||||
private DelayedSettlementAttributeService delayedSettlementAttributeService;
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferOutstandingInterestCalculation externalAssetOwnerTransferOutstandingInterestCalculation;
|
||||
|
||||
private LoanAccountOwnerTransferBusinessStep underTest;
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void destruct() {
|
||||
MONEY_HELPER.close();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||
@@ -108,7 +131,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
|
||||
underTest = new LoanAccountOwnerTransferBusinessStep(externalAssetOwnerTransferRepository,
|
||||
externalAssetOwnerTransferLoanMappingRepository, accountingService, businessEventNotifierService,
|
||||
loanTransferabilityService, delayedSettlementAttributeService);
|
||||
loanTransferabilityService, delayedSettlementAttributeService, externalAssetOwnerTransferOutstandingInterestCalculation);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
||||
+23
-1
@@ -27,11 +27,14 @@ import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -51,16 +54,20 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMapping
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMapping;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMappingRepository;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@@ -68,6 +75,19 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AccountingServiceImplTest {
|
||||
|
||||
private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void destruct() {
|
||||
MONEY_HELPER.close();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BUSINESS_DATE, LocalDate.of(2024, 9, 27))));
|
||||
@@ -517,6 +537,9 @@ class AccountingServiceImplTest {
|
||||
@Mock
|
||||
private FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository;
|
||||
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferOutstandingInterestCalculation externalAssetOwnerTransferOutstandingInterestCalculation;
|
||||
|
||||
@InjectMocks
|
||||
private AccountingServiceImpl testSubject;
|
||||
|
||||
@@ -540,7 +563,6 @@ class AccountingServiceImplTest {
|
||||
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanSummary.getTotalPrincipalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanSummary.getTotalInterestOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanSummary.getTotalFeeChargesOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanSummary.getTotalPenaltyChargesOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loan.getSummary()).thenReturn(loanSummary);
|
||||
|
||||
+24
-3
@@ -23,11 +23,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.data.domain.Sort.Direction.ASC;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
@@ -44,9 +47,12 @@ import org.apache.fineract.investor.domain.ExternalAssetOwnerTransfer;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository;
|
||||
import org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository;
|
||||
import org.apache.fineract.investor.domain.LoanOwnershipTransferBusinessEvent;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -54,7 +60,9 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -63,6 +71,7 @@ import org.springframework.data.jpa.domain.Specification;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class LoanAccountOwnerTransferServiceTest {
|
||||
|
||||
private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferRepository externalAssetOwnerTransferRepository;
|
||||
@Mock
|
||||
@@ -71,15 +80,27 @@ public class LoanAccountOwnerTransferServiceTest {
|
||||
private AccountingService accountingService;
|
||||
@Mock
|
||||
private BusinessEventNotifierService businessEventNotifierService;
|
||||
@Mock
|
||||
private ExternalAssetOwnerTransferOutstandingInterestCalculation externalAssetOwnerTransferOutstandingInterestCalculation;
|
||||
|
||||
private LoanAccountOwnerTransferService underTest;
|
||||
@InjectMocks
|
||||
private LoanAccountOwnerTransferServiceImpl underTest;
|
||||
private final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault());
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void destruct() {
|
||||
MONEY_HELPER.close();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BUSINESS_DATE, actualDate)));
|
||||
underTest = new LoanAccountOwnerTransferServiceImpl(externalAssetOwnerTransferRepository,
|
||||
externalAssetOwnerTransferLoanMappingRepository, accountingService, businessEventNotifierService);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+17
-5
@@ -42,14 +42,13 @@ import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
|
||||
import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData;
|
||||
import org.apache.fineract.portfolio.calendar.data.CalendarData;
|
||||
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
||||
import org.apache.fineract.portfolio.charge.util.ConvertChargeDataToSpecificChargeData;
|
||||
import org.apache.fineract.portfolio.client.data.ClientData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
|
||||
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
|
||||
import org.apache.fineract.portfolio.fund.data.FundData;
|
||||
import org.apache.fineract.portfolio.group.data.GroupGeneralData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.guarantor.data.GuarantorData;
|
||||
import org.apache.fineract.portfolio.loanaccount.guarantor.data.IGuarantor;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
|
||||
@@ -146,7 +145,7 @@ public class LoanAccountData {
|
||||
private Collection<LoanTransactionData> transactions;
|
||||
private Collection<LoanChargeData> charges;
|
||||
private Collection<LoanCollateralManagementData> collateral;
|
||||
private Collection<GuarantorData> guarantors;
|
||||
private Collection<? extends IGuarantor> guarantors;
|
||||
private CalendarData meeting;
|
||||
private Collection<NoteData> notes;
|
||||
private Collection<DisbursementData> disbursementDetails;
|
||||
@@ -356,7 +355,7 @@ public class LoanAccountData {
|
||||
final Collection<LoanChargeData> charges = new ArrayList<LoanChargeData>();
|
||||
for (final ChargeData charge : product.charges()) {
|
||||
if (!charge.isOverdueInstallmentCharge()) {
|
||||
charges.add(ConvertChargeDataToSpecificChargeData.toLoanChargeData(charge));
|
||||
charges.add(toLoanChargeData(charge));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +529,7 @@ public class LoanAccountData {
|
||||
*/
|
||||
public LoanAccountData associationsAndTemplate(final LoanScheduleData repaymentSchedule,
|
||||
final Collection<LoanTransactionData> transactions, final Collection<LoanChargeData> charges,
|
||||
final Collection<LoanCollateralManagementData> collateral, final Collection<GuarantorData> guarantors,
|
||||
final Collection<LoanCollateralManagementData> collateral, final Collection<? extends IGuarantor> guarantors,
|
||||
final CalendarData calendarData, final Collection<LoanProductData> productOptions,
|
||||
final Collection<EnumOptionData> termFrequencyTypeOptions, final Collection<EnumOptionData> repaymentFrequencyTypeOptions,
|
||||
final Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions,
|
||||
@@ -680,4 +679,17 @@ public class LoanAccountData {
|
||||
public boolean isActive() {
|
||||
return LoanStatus.fromInt(getStatus().getId().intValue()).isActive();
|
||||
}
|
||||
|
||||
public static LoanChargeData toLoanChargeData(final ChargeData chargeData) {
|
||||
|
||||
BigDecimal percentage = null;
|
||||
if (chargeData.getChargeCalculationType().getId() == 2) {
|
||||
percentage = chargeData.getAmount();
|
||||
}
|
||||
|
||||
return LoanChargeData.newLoanChargeDetails(chargeData.getId(), chargeData.getName(), chargeData.getCurrency(),
|
||||
chargeData.getAmount(), percentage, chargeData.getChargeTimeType(), chargeData.getChargeCalculationType(),
|
||||
chargeData.isPenalty(), chargeData.getChargePaymentMode(), chargeData.getMinCap(), chargeData.getMaxCap(),
|
||||
ExternalId.empty());
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.portfolio.loanaccount.guarantor.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface IGuarantor extends Serializable {}
|
||||
+6
@@ -44,6 +44,7 @@ import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
|
||||
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -69,6 +70,11 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
|
||||
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void destruct() {
|
||||
MONEY_HELPER.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnePeriodWithFeeCharge() {
|
||||
LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
|
||||
|
||||
+6
@@ -527,4 +527,10 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
public boolean isImmediateChargeAccrualPostMaturityEnabled() {
|
||||
return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY).isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetOwnerTransferOustandingInterestStrategy() {
|
||||
return getGlobalConfigurationPropertyData(
|
||||
GlobalConfigurationConstants.ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY).getStringValue();
|
||||
}
|
||||
}
|
||||
|
||||
-15
@@ -21,9 +21,7 @@ package org.apache.fineract.portfolio.charge.util;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
|
||||
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountChargeData;
|
||||
|
||||
@@ -31,19 +29,6 @@ public final class ConvertChargeDataToSpecificChargeData {
|
||||
|
||||
private ConvertChargeDataToSpecificChargeData() {}
|
||||
|
||||
public static LoanChargeData toLoanChargeData(final ChargeData chargeData) {
|
||||
|
||||
BigDecimal percentage = null;
|
||||
if (chargeData.getChargeCalculationType().getId() == 2) {
|
||||
percentage = chargeData.getAmount();
|
||||
}
|
||||
|
||||
return LoanChargeData.newLoanChargeDetails(chargeData.getId(), chargeData.getName(), chargeData.getCurrency(),
|
||||
chargeData.getAmount(), percentage, chargeData.getChargeTimeType(), chargeData.getChargeCalculationType(),
|
||||
chargeData.isPenalty(), chargeData.getChargePaymentMode(), chargeData.getMinCap(), chargeData.getMaxCap(),
|
||||
ExternalId.empty());
|
||||
}
|
||||
|
||||
public static SavingsAccountChargeData toSavingsAccountChargeData(final ChargeData chargeData) {
|
||||
|
||||
final Long savingsChargeId = null;
|
||||
|
||||
+1
-2
@@ -19,7 +19,6 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.guarantor.data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
@@ -34,7 +33,7 @@ import org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorType;
|
||||
import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorEnumerations;
|
||||
|
||||
@Getter
|
||||
public class GuarantorData implements Serializable {
|
||||
public class GuarantorData implements IGuarantor {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
+11
-2
@@ -105,8 +105,8 @@ public class GlobalConfigurationHelper {
|
||||
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
|
||||
GetGlobalConfigurationsResponse actualGlobalConfigurations = getAllGlobalConfigurations();
|
||||
|
||||
Assertions.assertEquals(56, expectedGlobalConfigurations.size());
|
||||
Assertions.assertEquals(56, actualGlobalConfigurations.getGlobalConfiguration().size());
|
||||
Assertions.assertEquals(57, expectedGlobalConfigurations.size());
|
||||
Assertions.assertEquals(57, actualGlobalConfigurations.getGlobalConfiguration().size());
|
||||
|
||||
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
|
||||
|
||||
@@ -540,6 +540,15 @@ public class GlobalConfigurationHelper {
|
||||
enableImmediateChargeAccrualPostMaturity.put("trapDoor", false);
|
||||
defaults.add(enableImmediateChargeAccrualPostMaturity);
|
||||
|
||||
HashMap<String, Object> assetOwnerTransferInterestOutstandingStrategy = new HashMap<>();
|
||||
assetOwnerTransferInterestOutstandingStrategy.put("name",
|
||||
GlobalConfigurationConstants.ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY);
|
||||
assetOwnerTransferInterestOutstandingStrategy.put("value", 0L);
|
||||
assetOwnerTransferInterestOutstandingStrategy.put("enabled", false);
|
||||
assetOwnerTransferInterestOutstandingStrategy.put("trapDoor", false);
|
||||
assetOwnerTransferInterestOutstandingStrategy.put("string_value", "TOTAL_OUTSTANDING_INTEREST");
|
||||
defaults.add(assetOwnerTransferInterestOutstandingStrategy);
|
||||
|
||||
return defaults;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user