FINERACT-1981: Update Payable Interest Calculation For LoanSummaryData
This commit is contained in:
+22
@@ -4674,6 +4674,28 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
||||
assertEquals(new BigDecimal("0.05"), loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
});
|
||||
|
||||
runAt("31 January 2024", () -> {
|
||||
// Generate the Accruals
|
||||
final PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(requestSpec,
|
||||
responseSpec);
|
||||
periodicAccrualAccountingHelper.runPeriodicAccrualAccounting("31 January 2024");
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
assertEquals(new BigDecimal("0.09"), loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
});
|
||||
|
||||
runAt("1 February 2024", () -> {
|
||||
// Generate the Accruals
|
||||
final PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(requestSpec,
|
||||
responseSpec);
|
||||
periodicAccrualAccountingHelper.runPeriodicAccrualAccounting("1 February 2024");
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
assertEquals(new BigDecimal("0.12"), loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
});
|
||||
|
||||
// Not Due and Due Interest
|
||||
runAt("20 February 2024", () -> {
|
||||
final PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(requestSpec,
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
@Slf4j
|
||||
public class LoanPrepayAmountTest extends BaseLoanIntegrationTest {
|
||||
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
Long loanId;
|
||||
|
||||
@Test
|
||||
public void testLoanPrepayAmountProgressive() {
|
||||
runAt("1 January 2024", () -> {
|
||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
|
||||
loanProductsResponse.getResourceId(), "01 January 2024", 1000.0, 9.99, 6, null));
|
||||
loanId = postLoansResponse.getLoanId();
|
||||
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(1000.0, "01 January 2024"));
|
||||
disburseLoan(loanId, BigDecimal.valueOf(250.0), "01 January 2024");
|
||||
});
|
||||
runAt("7 january 2024", () -> {
|
||||
disburseLoan(loanId, BigDecimal.valueOf(350.0), "04 January 2024");
|
||||
disburseLoan(loanId, BigDecimal.valueOf(400.0), "05 January 2024");
|
||||
});
|
||||
for (int i = 7; i <= 31; i++) {
|
||||
runAt(i + " January 2024", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
HashMap prepayAmount = loanTransactionHelper.getPrepayAmount(requestSpec, responseSpec, loanId.intValue());
|
||||
Assertions.assertEquals((float) prepayAmount.get("interestPortion"),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().floatValue());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-177
@@ -18,11 +18,6 @@
|
||||
*/
|
||||
package org.apache.fineract.integrationtests;
|
||||
|
||||
import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.InterestRateFrequencyType.YEARS;
|
||||
import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.RepaymentFrequencyType.DAYS;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.restassured.builder.RequestSpecBuilder;
|
||||
import io.restassured.builder.ResponseSpecBuilder;
|
||||
import io.restassured.http.ContentType;
|
||||
@@ -30,22 +25,16 @@ import io.restassured.specification.RequestSpecification;
|
||||
import io.restassured.specification.ResponseSpecification;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -78,158 +67,6 @@ public class LoanRefundTransactionTest extends BaseLoanIntegrationTest {
|
||||
loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(requestSpec, responseSpec);
|
||||
}
|
||||
|
||||
// UC1: (Internal case) Generate a totalInterestRefund using Advanced payment allocation with Interest Refund
|
||||
// options and Apply
|
||||
// Interest Refund transaction
|
||||
// 1. Create a Loan product with Adv. Pment. Alloc. and with Accrual and Interest Refund
|
||||
// 2. Submit, Approve and Disburse a Loan account
|
||||
// 3. Apply the INTEREST_REFUND transaction to validate the Journal Entries generated
|
||||
@Test
|
||||
public void uc1() {
|
||||
final String operationDate = "1 January 2024";
|
||||
AtomicLong createdLoanId = new AtomicLong();
|
||||
runAt(operationDate, () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
||||
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.DAYS_360)
|
||||
.numberOfRepayments(4)//
|
||||
.maxInterestRatePerPeriod((double) 110)//
|
||||
.repaymentEvery(1)//
|
||||
.repaymentFrequencyType(1L)//
|
||||
.allowPartialPeriodInterestCalcualtion(false)//
|
||||
.multiDisburseLoan(false)//
|
||||
.disallowExpectedDisbursements(null)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(null)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.overAppliedNumber(null)//
|
||||
.installmentAmountInMultiplesOf(null)//
|
||||
;//
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate, 1000.0, 4)
|
||||
.interestRatePerPeriod(BigDecimal.valueOf(108.0));
|
||||
|
||||
applicationRequest = applicationRequest.interestCalculationPeriodType(DAYS)
|
||||
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
|
||||
|
||||
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
createdLoanId.set(loanResponse.getLoanId());
|
||||
|
||||
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
|
||||
.approvedOnDate("1 January 2024").locale("en"));
|
||||
|
||||
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||
new PostLoansLoanIdRequest().actualDisbursementDate("1 January 2024").dateFormat(DATETIME_PATTERN)
|
||||
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||
|
||||
// After Disbursement we are expecting zero interest refund
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||
assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
});
|
||||
|
||||
runAt("10 February 2024", () -> {
|
||||
// After Interest refund transaction we are expecting non zero interest refund
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
LOG.info("value {}", loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
|
||||
final BigDecimal inteterestRefund = BigDecimal.valueOf(11);
|
||||
final Long loanTransactionId = loanTransactionHelper.applyInterestRefundLoanTransaction(requestSpec, responseSpec,
|
||||
createdLoanId.get(), buildJsonBody(BigDecimal.valueOf(5.0), BigDecimal.valueOf(3.0), BigDecimal.valueOf(2.0),
|
||||
BigDecimal.valueOf(1.0), null));
|
||||
|
||||
loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
assertEquals(inteterestRefund, loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
|
||||
verifyTRJournalEntries(loanTransactionId, journalEntry(5.0, loansReceivableAccount, "CREDIT"),
|
||||
journalEntry(3.0, interestReceivableAccount, "CREDIT"), journalEntry(2.0, feeReceivableAccount, "CREDIT"),
|
||||
journalEntry(1.0, penaltyReceivableAccount, "CREDIT"), journalEntry(11.0, overpaymentAccount, "CREDIT"),
|
||||
journalEntry(22.0, interestIncomeAccount, "DEBIT"));
|
||||
});
|
||||
}
|
||||
|
||||
// UC2: (Internal case) Generate a totalInterestRefund using Advanced payment allocation with Interest Refund
|
||||
// options and Apply
|
||||
// Interest Refund transaction when the Loan Account is ChargeBack
|
||||
// 1. Create a Loan product with Adv. Pment. Alloc. and with Accrual and Interest Refund
|
||||
// 2. Submit, Approve and Disburse a Loan account
|
||||
// 3. Apply the MERCHANT_ISSUED_REFUND transaction to validate totalInterestRefund different than Zero
|
||||
// 4- Apply the INTEREST_REFUND transaction to validate the Journal Entries generated
|
||||
@Test
|
||||
public void uc2() {
|
||||
final String operationDate = "1 January 2024";
|
||||
AtomicLong createdLoanId = new AtomicLong();
|
||||
runAt(operationDate, () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
|
||||
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.DAYS_360)
|
||||
.numberOfRepayments(4)//
|
||||
.maxInterestRatePerPeriod((double) 110)//
|
||||
.repaymentEvery(1)//
|
||||
.repaymentFrequencyType(1L)//
|
||||
.allowPartialPeriodInterestCalcualtion(false)//
|
||||
.multiDisburseLoan(false)//
|
||||
.disallowExpectedDisbursements(null)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(null)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.overAppliedNumber(null)//
|
||||
.installmentAmountInMultiplesOf(null)//
|
||||
;//
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate, 1000.0, 4)
|
||||
.interestRatePerPeriod(BigDecimal.valueOf(108.0));
|
||||
|
||||
applicationRequest = applicationRequest.interestCalculationPeriodType(DAYS)
|
||||
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
|
||||
|
||||
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
createdLoanId.set(loanResponse.getLoanId());
|
||||
|
||||
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
|
||||
.approvedOnDate("1 January 2024").locale("en"));
|
||||
|
||||
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||
new PostLoansLoanIdRequest().actualDisbursementDate("1 January 2024").dateFormat(DATETIME_PATTERN)
|
||||
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||
|
||||
// After Disbursement we are expecting zero interest refund
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||
assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
});
|
||||
|
||||
runAt("1 February 2024", () -> {
|
||||
// After Interest refund transaction we are expecting non zero interest refund
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
LOG.info("value {}", loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
|
||||
Long repayment1TransactionId = addRepaymentForLoan(createdLoanId.get(), 250.0, "1 February 2024");
|
||||
chargeOffLoan(createdLoanId.get(), "1 February 2024");
|
||||
});
|
||||
|
||||
runAt("10 February 2024", () -> {
|
||||
// After Interest refund transaction we are expecting non zero interest refund
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
LOG.info("value {}", loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
LOG.info("chargedOffOnDate {}", loanDetails.getTimeline().getChargedOffOnDate());
|
||||
assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
|
||||
final BigDecimal inteterestRefund = BigDecimal.valueOf(11);
|
||||
final Long loanTransactionId = loanTransactionHelper.applyInterestRefundLoanTransaction(requestSpec, responseSpec,
|
||||
createdLoanId.get(), buildJsonBody(BigDecimal.valueOf(5.0), BigDecimal.valueOf(3.0), BigDecimal.valueOf(2.0),
|
||||
BigDecimal.valueOf(1.0), null));
|
||||
|
||||
loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
assertEquals(inteterestRefund, loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||
|
||||
verifyTRJournalEntries(loanTransactionId, journalEntry(11.0, interestIncomeChargeOffAccount, "CREDIT"),
|
||||
journalEntry(11.0, overpaymentAccount, "CREDIT"), journalEntry(22.0, interestIncomeAccount, "DEBIT"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMerchantIssuedRefundCreatesAndReversesInterestRefund() {
|
||||
runAt("01 July 2024", () -> {
|
||||
@@ -292,20 +129,6 @@ public class LoanRefundTransactionTest extends BaseLoanIntegrationTest {
|
||||
});
|
||||
}
|
||||
|
||||
private String buildJsonBody(final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
||||
final BigDecimal penaltyCharges, final BigDecimal overpayment) {
|
||||
final HashMap<String, BigDecimal> map = new HashMap<>();
|
||||
map.put("principal", principal);
|
||||
map.put("interest", interest);
|
||||
map.put("feeCharges", feeCharges);
|
||||
map.put("penaltyCharges", penaltyCharges);
|
||||
if (overpayment != null) {
|
||||
map.put("overpayment", overpayment);
|
||||
}
|
||||
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
|
||||
private Long createLoanForMerchantIssuedRefundWithInterestRefund(Long clientId) {
|
||||
return createLoanForRefundWithInterestRefund(clientId, "MERCHANT_ISSUED_REFUND");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user