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 a078cfd8c FINERACT-1971: Accrual for interest bearing loan with charge
accrual based on submitted On Date fix
a078cfd8c is described below
commit a078cfd8c5edfaa4d8fc1923b8fac7db5264997b
Author: Ruchi Dhamankar <[email protected]>
AuthorDate: Fri May 31 19:04:55 2024 +0530
FINERACT-1971: Accrual for interest bearing loan with charge accrual based
on submitted On Date fix
---
.../portfolio/loanaccount/domain/Loan.java | 10 +-
...tAndChargeAccrualDateAsSubmittedOnDateTest.java | 246 +++++++++++++++++++++
2 files changed, 253 insertions(+), 3 deletions(-)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 0fb9c31a7..3043db27f 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -1358,9 +1358,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
Money fee = Money.zero(getCurrency());
Money penality = Money.zero(getCurrency());
for (LoanTransaction loanTransaction : accruals) {
- LocalDate transactionDateForRange = isBasedOnSubmittedOnDate
- ?
loanTransaction.getLoanChargesPaid().stream().findFirst().get().getLoanCharge().getDueDate()
- : loanTransaction.getTransactionDate();
+ LocalDate transactionDateForRange =
getDateForRangeCalculation(loanTransaction, isBasedOnSubmittedOnDate);
boolean isInPeriod =
LoanRepaymentScheduleProcessingWrapper.isInPeriod(transactionDateForRange,
installment, installments);
if (isInPeriod) {
interest =
interest.plus(loanTransaction.getInterestPortion(getCurrency()));
@@ -1389,6 +1387,12 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
}
}
+ private LocalDate getDateForRangeCalculation(LoanTransaction
loanTransaction, boolean isChargeAccrualBasedOnSubmittedOnDate) {
+ return isChargeAccrualBasedOnSubmittedOnDate &&
!loanTransaction.getLoanChargesPaid().isEmpty()
+ ?
loanTransaction.getLoanChargesPaid().stream().findFirst().get().getLoanCharge().getEffectiveDueDate()
+ : loanTransaction.getTransactionDate();
+ }
+
private void updateAccrualsForNonPeriodicAccruals(final
Collection<LoanTransaction> accruals) {
final Money interestApplied = Money.of(getCurrency(),
this.summary.getTotalInterestCharged());
ExternalId externalId = ExternalId.empty();
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccrualTransactionWithInterestAndChargeAccrualDateAsSubmittedOnDateTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccrualTransactionWithInterestAndChargeAccrualDateAsSubmittedOnDateTest.java
new file mode 100644
index 000000000..87d6a87aa
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccrualTransactionWithInterestAndChargeAccrualDateAsSubmittedOnDateTest.java
@@ -0,0 +1,246 @@
+/**
+ * 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 static
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY;
+
+import java.math.BigDecimal;
+import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.junit.jupiter.api.Test;
+
+public class
LoanAccrualTransactionWithInterestAndChargeAccrualDateAsSubmittedOnDateTest
extends BaseLoanIntegrationTest {
+
+ private SchedulerJobHelper schedulerJobHelper = new
SchedulerJobHelper(this.requestSpec);
+
+ @Test
+ public void
accrualTransactionForInterestBearingLoan_WithoutCharges_SubmittedOnDateAsChargeAccrualDateWorksTest()
{
+ runAt("15 April 2024", () -> {
+
+ try {
+ // Configure Charge accrual date as submitted on date
+
GlobalConfigurationHelper.updateChargeAccrualDateConfiguration(this.requestSpec,
this.responseSpec, "submitted-date");
+
+ // Create Client
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+ // Create Loan Product
+ PostLoanProductsRequest loanProductsRequest =
createLoanProductWithInterestCalculation();
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+ // Apply and Approve Loan
+ Long loanId = applyAndApproveLoanApplication(clientId,
loanProductResponse.getResourceId(), "15 April 2024", 1000.0, 4);
+
+ // Disburse Loan
+ disburseLoan(loanId, BigDecimal.valueOf(500), "15 April 2024");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(500.0, null, "15 April 2024"), //
+ installment(114.41, 29.59, 0.0, 0.0, 144.0, false, "30
April 2024"), //
+ installment(121.18, 22.82, 0.0, 0.0, 144.0, false, "15
May 2024"), //
+ installment(128.35, 15.65, 0.0, 0.0, 144.0, false, "30
May 2024"), //
+ installment(136.06, 8.05, 0.0, 0.0, 144.11, false, "14
June 2024") //
+ );
+
+ verifyTransactions(loanId, //
+ transaction(500.0, "Disbursement", "15 April 2024",
500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
+
+ // update business date
+ updateBusinessDate("25 April 2024");
+
+ // run cob
+ schedulerJobHelper.executeAndAwaitJob("Loan COB");
+
+ verifyTransactions(loanId, //
+ transaction(500.0, "Disbursement", "15 April 2024",
500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(17.75, "Accrual", "24 April 2024", 0.0,
0.0, 17.75, 0.0, 0.0, 0.0, 0.0) //
+ );
+
+ // update business date
+ updateBusinessDate("26 April 2024");
+
+ // disburse amount
+ disburseLoan(loanId, BigDecimal.valueOf(500), "26 April 2024");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(500.0, null, "15 April 2024"), //
+ installment(500.0, null, "26 April 2024"), //
+ installment(250.52, 37.48, 0.0, 0.0, 288.0, false, "30
April 2024"), //
+ installment(243.65, 44.35, 0.0, 0.0, 288.0, false, "15
May 2024"), //
+ installment(258.07, 29.93, 0.0, 0.0, 288.0, false, "30
May 2024"), //
+ installment(247.76, 14.66, 0.0, 0.0, 262.42, false,
"14 June 2024") //
+ );
+
+ verifyTransactions(loanId, //
+ transaction(500.0, "Disbursement", "15 April 2024",
500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(22.49, "Accrual", "24 April 2024", 0.0,
0.0, 22.49, 0.0, 0.0, 0.0, 0.0), //
+ transaction(500.0, "Disbursement", "26 April 2024",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+
+ } finally {
+
GlobalConfigurationHelper.updateChargeAccrualDateConfiguration(this.requestSpec,
this.responseSpec, "due-date");
+ }
+
+ });
+
+ }
+
+ @Test
+ public void
accrualTransactionForInterestBearingLoan_WithCharges_SubmittedOnDateAsChargeAccrualDateWorksTest()
{
+ runAt("15 April 2024", () -> {
+
+ try {
+ // Configure Charge accrual date as submitted on date
+
GlobalConfigurationHelper.updateChargeAccrualDateConfiguration(this.requestSpec,
this.responseSpec, "submitted-date");
+
+ // Create Client
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+ // Create Loan Product
+ PostLoanProductsRequest loanProductsRequest =
createLoanProductWithInterestCalculation();
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+ // Apply and Approve Loan
+ Long loanId = applyAndApproveLoanApplication(clientId,
loanProductResponse.getResourceId(), "15 April 2024", 1000.0, 4);
+
+ // Disburse Loan
+ disburseLoan(loanId, BigDecimal.valueOf(500), "15 April 2024");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(500.0, null, "15 April 2024"), //
+ installment(114.41, 29.59, 0.0, 0.0, 144.0, false, "30
April 2024"), //
+ installment(121.18, 22.82, 0.0, 0.0, 144.0, false, "15
May 2024"), //
+ installment(128.35, 15.65, 0.0, 0.0, 144.0, false, "30
May 2024"), //
+ installment(136.06, 8.05, 0.0, 0.0, 144.11, false, "14
June 2024") //
+ );
+
+ verifyTransactions(loanId, //
+ transaction(500.0, "Disbursement", "15 April 2024",
500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
+ );
+
+ // update business date
+ updateBusinessDate("24 April 2024");
+
+ // add charge
+ addCharge(loanId, false, 10.0, "29 April 2024");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(500.0, null, "15 April 2024"), //
+ installment(114.41, 29.59, 10.0, 0.0, 154.0, false,
"30 April 2024"), //
+ installment(121.18, 22.82, 0.0, 0.0, 144.0, false, "15
May 2024"), //
+ installment(128.35, 15.65, 0.0, 0.0, 144.0, false, "30
May 2024"), //
+ installment(136.06, 8.05, 0.0, 0.0, 144.11, false, "14
June 2024") //
+ );
+
+ // update business date
+ updateBusinessDate("25 April 2024");
+
+ // run cob
+ schedulerJobHelper.executeAndAwaitJob("Loan COB");
+
+ verifyTransactions(loanId, //
+ transaction(500.0, "Disbursement", "15 April 2024",
500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(27.75, "Accrual", "24 April 2024", 0.0,
0.0, 17.75, 10.0, 0.0, 0.0, 0.0) //
+ );
+
+ // update business date
+ updateBusinessDate("26 April 2024");
+
+ // disburse amount
+ disburseLoan(loanId, BigDecimal.valueOf(500), "26 April 2024");
+
+ // Verify Repayment Schedule and Due Dates
+ verifyRepaymentSchedule(loanId, //
+ installment(500.0, null, "15 April 2024"), //
+ installment(500.0, null, "26 April 2024"), //
+ installment(250.52, 37.48, 10.0, 0.0, 298.0, false,
"30 April 2024"), //
+ installment(243.65, 44.35, 0.0, 0.0, 288.0, false, "15
May 2024"), //
+ installment(258.07, 29.93, 0.0, 0.0, 288.0, false, "30
May 2024"), //
+ installment(247.76, 14.66, 0.0, 0.0, 262.42, false,
"14 June 2024") //
+ );
+
+ verifyTransactions(loanId, //
+ transaction(500.0, "Disbursement", "15 April 2024",
500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(32.49, "Accrual", "24 April 2024", 0.0,
0.0, 22.49, 10.0, 0.0, 0.0, 0.0), //
+ transaction(500.0, "Disbursement", "26 April 2024",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+
+ // run cob
+ schedulerJobHelper.executeAndAwaitJob("Loan COB");
+
+ verifyTransactions(loanId, //
+ transaction(500.0, "Disbursement", "15 April 2024",
500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
+ transaction(32.49, "Accrual", "24 April 2024", 0.0,
0.0, 22.49, 10.0, 0.0, 0.0, 0.0), //
+ transaction(2.50, "Accrual", "25 April 2024", 0.0,
0.0, 2.50, 0.0, 0.0, 0.0, 0.0), //
+ transaction(500.0, "Disbursement", "26 April 2024",
1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+
+ } finally {
+
GlobalConfigurationHelper.updateChargeAccrualDateConfiguration(this.requestSpec,
this.responseSpec, "due-date");
+ }
+
+ });
+
+ }
+
+ private Long applyAndApproveLoanApplication(Long clientId, Long productId,
String disbursementDate, double amount,
+ int numberOfRepayments) {
+ PostLoansRequest postLoansRequest = new
PostLoansRequest().clientId(clientId).productId(productId)
+
.expectedDisbursementDate(disbursementDate).dateFormat(DATETIME_PATTERN)
+
.transactionProcessingStrategyCode(DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY)
+
.locale("en").submittedOnDate(disbursementDate).amortizationType(AmortizationType.EQUAL_INSTALLMENTS)
+ .interestRatePerPeriod(new BigDecimal(12.0))
+
.interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)
+
.interestType(InterestType.DECLINING_BALANCE).repaymentEvery(15).repaymentFrequencyType(RepaymentFrequencyType.DAYS)
+
.numberOfRepayments(numberOfRepayments).loanTermFrequency(numberOfRepayments *
15).loanTermFrequencyType(0)
+
.maxOutstandingLoanBalance(BigDecimal.valueOf(amount)).principal(BigDecimal.valueOf(amount)).loanType("individual");
+ PostLoansResponse postLoansResponse =
loanTransactionHelper.applyLoan(postLoansRequest);
+ PostLoansLoanIdResponse approvedLoanResult =
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
+ approveLoanRequest(amount, disbursementDate));
+ return approvedLoanResult.getLoanId();
+ }
+
+ private PostLoanProductsRequest createLoanProductWithInterestCalculation()
{
+ return
createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().multiDisburseLoan(true)//
+ .disallowExpectedDisbursements(true)//
+ .allowApprovedDisbursedAmountsOverApplied(false)//
+ .overAppliedCalculationType(null)//
+ .overAppliedNumber(null)//
+ .principal(1000.0)//
+ .numberOfRepayments(4)//
+ .repaymentEvery(15)//
+
.repaymentFrequencyType(RepaymentFrequencyType.DAYS.longValue())//
+ .interestType(InterestType.DECLINING_BALANCE)//
+ .amortizationType(AmortizationType.EQUAL_INSTALLMENTS)//
+
.interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)//
+ .interestRatePerPeriod(12.0) //
+ .interestRateFrequencyType(InterestRateFrequencyType.MONTHS)//
+ .isInterestRecalculationEnabled(true) //
+
.interestRecalculationCompoundingMethod(0).rescheduleStrategyMethod(3).recalculationRestFrequencyType(1)
+ .recalculationRestFrequencyInterval(1);
+ }
+}