Skip to content

Commit

Permalink
FINERACT-1971: Accrual for interest bearing loan with charge accrual …
Browse files Browse the repository at this point in the history
…based on submitted On Date fix
  • Loading branch information
ruchiD committed Jun 3, 2024
1 parent 488db10 commit acc1edc
Show file tree
Hide file tree
Showing 2 changed files with 253 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1358,9 +1358,7 @@ private void applyPeriodicAccruals(final Collection<LoanTransaction> accruals) {
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()));
Expand Down Expand Up @@ -1389,6 +1387,12 @@ private void applyPeriodicAccruals(final Collection<LoanTransaction> accruals) {
}
}

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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit acc1edc

Please sign in to comment.