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 5ced30b86 FINERACT-1724: Create accrual entry for specific due date
charge even on disbursement date
5ced30b86 is described below
commit 5ced30b864d39b95f02c8588f05224ee4d050419
Author: Adam Saghy <[email protected]>
AuthorDate: Fri Mar 10 20:12:06 2023 +0100
FINERACT-1724: Create accrual entry for specific due date charge even on
disbursement date
---
.../LoanAccrualWritePlatformServiceImpl.java | 3 +-
.../service/LoanReadPlatformServiceImpl.java | 3 +-
.../LoanChargeSpecificDueDateTest.java | 44 ++++++++-
.../integrationtests/SchedulerJobsTestResults.java | 101 +++++++++++++++++++--
4 files changed, 141 insertions(+), 10 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
index f0c46d89c..bc7e40694 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
@@ -375,7 +375,8 @@ public class LoanAccrualWritePlatformServiceImpl implements
LoanAccrualWritePlat
}
}
}
- } else if (loanCharge.getDueDate().isAfter(startDate) &&
!loanCharge.getDueDate().isAfter(endDate)) {
+ } else if (((accrualData.getInstallmentNumber() == 1 &&
loanCharge.getDueDate().isEqual(startDate))
+ || loanCharge.getDueDate().isAfter(startDate)) &&
!loanCharge.getDueDate().isAfter(endDate)) {
chargeAmount = loanCharge.getAmount();
if (loanCharge.getAmountUnrecognized() != null) {
chargeAmount =
chargeAmount.subtract(loanCharge.getAmountUnrecognized());
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index f5113daa3..98d5e3280 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -1778,7 +1778,8 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService {
.append(" or (ls.penalty_charges_amount <>
COALESCE(ls.accrual_penalty_charges_derived, 0))")
.append(" or (ls.interest_amount <>
COALESCE(ls.accrual_interest_derived, 0)))")
.append(" and loan.loan_status_id=:active and
mpl.accounting_type=:type and (loan.closedon_date <= :tillDate or
loan.closedon_date is null)")
- .append(" and loan.is_npa=false and (ls.duedate <= :tillDate
or (ls.duedate > :tillDate and ls.fromdate < :tillDate))) ");
+ .append(" and loan.is_npa=false and (ls.duedate <= :tillDate
or (ls.duedate > :tillDate and ls.fromdate < :tillDate)")
+ .append(" or (ls.installment = 1 and ls.fromdate =
:tillDate))) ");
Map<String, Object> paramMap = new HashMap<>(5);
if (organisationStartDate != null) {
sqlBuilder.append(" and ls.duedate > :organisationStartDate ");
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
index 02393b488..c4c65a50a 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
@@ -87,7 +87,8 @@ public class LoanChargeSpecificDueDateTest {
// Client and Loan account creation
final Integer clientId = ClientHelper.createClient(this.requestSpec,
this.responseSpec, "01 January 2012");
- final GetLoanProductsProductIdResponse getLoanProductsProductResponse
= createLoanProduct(loanTransactionHelper, null);
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse
= createLoanProductWithPeriodicAccrual(loanTransactionHelper,
+ null);
assertNotNull(getLoanProductsProductResponse);
// Older date to have more than one overdue installment
@@ -120,6 +121,25 @@ public class LoanChargeSpecificDueDateTest {
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec,
responseSpec, loanId);
validateLoanAccount(getLoansLoanIdResponse,
Double.valueOf(principalAmount), Double.valueOf("10.00"), false);
+ // Run Accruals
+ log.info("Running Periodic Accrual for date {}", transactionDate);
+
periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(operationDate);
+ getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec,
responseSpec, loanId);
+
+ final Long transactionId =
loanTransactionHelper.evaluateLastLoanTransactionData(getLoansLoanIdResponse,
+ "loanTransactionType.accrual", operationDate,
Double.valueOf("10.00"));
+ assertNotNull(transactionId);
+ log.info("transactionId {}", transactionId);
+
+ final GetJournalEntriesTransactionIdResponse journalEntriesResponse =
journalEntryHelper.getJournalEntries("L" + transactionId);
+ assertNotNull(journalEntriesResponse);
+ final List<JournalEntryTransactionItem> journalEntries =
journalEntriesResponse.getPageItems();
+ assertEquals(2, journalEntries.size());
+ assertEquals(10, journalEntries.get(0).getAmount());
+ assertEquals(10, journalEntries.get(1).getAmount());
+ assertEquals(transactionDate,
journalEntries.get(0).getTransactionDate());
+ assertEquals(transactionDate,
journalEntries.get(1).getTransactionDate());
+
// Make a full repayment to close the Loan
Float amount = Float.valueOf("1010.00");
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse =
loanTransactionHelper.makeLoanRepayment(operationDate, amount,
@@ -140,7 +160,8 @@ public class LoanChargeSpecificDueDateTest {
// Client and Loan account creation
final Integer clientId = ClientHelper.createClient(this.requestSpec,
this.responseSpec, "01 January 2012");
- final GetLoanProductsProductIdResponse getLoanProductsProductResponse
= createLoanProduct(loanTransactionHelper, null);
+ final GetLoanProductsProductIdResponse getLoanProductsProductResponse
= createLoanProductWithPeriodicAccrual(loanTransactionHelper,
+ null);
assertNotNull(getLoanProductsProductResponse);
// Older date to have more than one overdue installment
@@ -174,6 +195,25 @@ public class LoanChargeSpecificDueDateTest {
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec,
responseSpec, loanId);
validateLoanAccount(getLoansLoanIdResponse,
Double.valueOf(principalAmount), Double.valueOf("10.00"), true);
+ // Run Accruals
+ log.info("Running Periodic Accrual for date {}", transactionDate);
+
periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(operationDate);
+ getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec,
responseSpec, loanId);
+
+ final Long transactionId =
loanTransactionHelper.evaluateLastLoanTransactionData(getLoansLoanIdResponse,
+ "loanTransactionType.accrual", operationDate,
Double.valueOf("10.00"));
+ assertNotNull(transactionId);
+ log.info("transactionId {}", transactionId);
+
+ final GetJournalEntriesTransactionIdResponse journalEntriesResponse =
journalEntryHelper.getJournalEntries("L" + transactionId);
+ assertNotNull(journalEntriesResponse);
+ final List<JournalEntryTransactionItem> journalEntries =
journalEntriesResponse.getPageItems();
+ assertEquals(2, journalEntries.size());
+ assertEquals(10, journalEntries.get(0).getAmount());
+ assertEquals(10, journalEntries.get(1).getAmount());
+ assertEquals(transactionDate,
journalEntries.get(0).getTransactionDate());
+ assertEquals(transactionDate,
journalEntries.get(1).getTransactionDate());
+
// Make a full repayment to close the Loan
Float amount = Float.valueOf("1010.00");
PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse =
loanTransactionHelper.makeLoanRepayment(operationDate, amount,
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
index 42785222d..1a7502c9c 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java
@@ -19,6 +19,8 @@
package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.common.truth.Truth;
import io.restassured.builder.RequestSpecBuilder;
@@ -48,7 +50,9 @@ import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import org.apache.fineract.client.models.BusinessDateRequest;
+import
org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.JournalEntryTransactionItem;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PutJobsJobIDRequest;
@@ -661,11 +665,15 @@ public class SchedulerJobsTestResults {
ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentage("1"));
Assertions.assertNotNull(overdueFeeChargeId);
+ Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
+
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
"10", false));
+ Assertions.assertNotNull(fee);
+
final Integer loanProductID =
createLoanProduct(overdueFeeChargeId.toString());
Assertions.assertNotNull(loanProductID);
List<Integer> loanIDs = new ArrayList<>();
HashMap loanStatusHashMap;
- for (int i = 0; i < 10; i++) {
+ for (int i = 0; i < 3; i++) {
final Integer loanID =
applyForLoanApplication(clientID.toString(), loanProductID.toString(), null,
"10 January 2020");
Assertions.assertNotNull(loanID);
@@ -682,6 +690,8 @@ public class SchedulerJobsTestResults {
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
loanIDs.add(loanID);
+ this.loanTransactionHelper.addChargesForLoan(loanID,
+
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee),
"02 March 2020", "10", null));
}
String jobName = "Loan COB";
@@ -690,18 +700,82 @@ public class SchedulerJobsTestResults {
List<HashMap> repaymentScheduleDataAfter =
this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec,
loanId);
+ Assertions.assertEquals(10.00f, (Float)
repaymentScheduleDataAfter.get(1).get("feeChargesDue"),
+ "Verifying From Fee Charges due for first Repayment after
Successful completion of Scheduler Job");
Assertions.assertEquals(39.39f, (Float)
repaymentScheduleDataAfter.get(1).get("penaltyChargesDue"),
- "Verifying From Penalty Charges due fot first Repayment
after Successful completion of Scheduler Job");
+ "Verifying From Penalty Charges due for first Repayment
after Successful completion of Scheduler Job");
Assertions.assertEquals(39.39f, (Float)
repaymentScheduleDataAfter.get(2).get("penaltyChargesDue"),
- "Verifying From Penalty Charges due fot first Repayment
after Successful completion of Scheduler Job");
+ "Verifying From Penalty Charges due for first Repayment
after Successful completion of Scheduler Job");
Assertions.assertEquals(39.39f, (Float)
repaymentScheduleDataAfter.get(3).get("penaltyChargesDue"),
- "Verifying From Penalty Charges due fot first Repayment
after Successful completion of Scheduler Job");
+ "Verifying From Penalty Charges due for first Repayment
after Successful completion of Scheduler Job");
Assertions.assertEquals(39.39f, (Float)
repaymentScheduleDataAfter.get(4).get("penaltyChargesDue"),
- "Verifying From Penalty Charges due fot first Repayment
after Successful completion of Scheduler Job");
+ "Verifying From Penalty Charges due for first Repayment
after Successful completion of Scheduler Job");
}
}
+ @Test
+ public void testLoanCOBJobOutcomeWhileAddingFeeOnDisbursementDate() {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.TRUE);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.COB_DATE, LocalDate.of(2020, 3, 2));
+ this.savingsAccountHelper = new SavingsAccountHelper(requestSpec,
responseSpec);
+ this.loanTransactionHelper = new LoanTransactionHelper(requestSpec,
responseSpec);
+
+ final Integer clientID = ClientHelper.createClient(requestSpec,
responseSpec);
+ Assertions.assertNotNull(clientID);
+
+ Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
+
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
"10", false));
+ Assertions.assertNotNull(fee);
+
+ final Integer loanProductID =
createLoanProductWithPeriodicAccrual(null);
+ Assertions.assertNotNull(loanProductID);
+
+ final Integer loanID = applyForLoanApplication(clientID.toString(),
loanProductID.toString(), null, "10 January 2020");
+
+ Assertions.assertNotNull(loanID);
+
+ HashMap loanStatusHashMap =
LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
+ LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+ loanStatusHashMap = this.loanTransactionHelper.approveLoan("01 March
2020", loanID);
+ LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+
+ String loanDetails =
this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
+ loanStatusHashMap =
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("02 March 2020",
loanID,
+
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
+ LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+ this.loanTransactionHelper.addChargesForLoan(loanID,
+
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee),
"02 March 2020", "10", null));
+
+ String jobName = "Loan COB";
+ this.schedulerJobHelper.executeAndAwaitJob(jobName);
+
+ List<HashMap> repaymentScheduleDataAfter =
this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec,
loanID);
+
+ Assertions.assertEquals(10.00f, (Float)
repaymentScheduleDataAfter.get(1).get("feeChargesDue"),
+ "Verifying From Fee Charges due for first Repayment after
Successful completion of Scheduler Job");
+
+ GetLoansLoanIdResponse getLoansLoanIdResponse =
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+ // First accrual transaction
+
assertTrue(getLoansLoanIdResponse.getTransactions().get(1).getType().getAccrual());
+ assertEquals(10.0f,
getLoansLoanIdResponse.getTransactions().get(1).getFeeChargesPortion());
+ assertEquals(LocalDate.of(2020, 3, 2),
getLoansLoanIdResponse.getTransactions().get(1).getDate());
+ Long transactionId =
getLoansLoanIdResponse.getTransactions().get(1).getId();
+
+ final GetJournalEntriesTransactionIdResponse journalEntriesResponse =
journalEntryHelper.getJournalEntries("L" + transactionId);
+ assertNotNull(journalEntriesResponse);
+ final List<JournalEntryTransactionItem> journalEntries =
journalEntriesResponse.getPageItems();
+ assertEquals(2, journalEntries.size());
+ assertEquals(10, journalEntries.get(0).getAmount());
+ assertEquals(10, journalEntries.get(1).getAmount());
+ assertEquals(LocalDate.of(2020, 3, 2),
journalEntries.get(1).getTransactionDate());
+ assertEquals(LocalDate.of(2020, 3, 2),
journalEntries.get(0).getTransactionDate());
+
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.FALSE);
+ }
+
@Test
public void testLoanCOBRunsOnlyOnLoansOneDayBehind() {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.TRUE);
@@ -934,7 +1008,7 @@ public class SchedulerJobsTestResults {
String JobName = "Update Non Performing Assets";
this.schedulerJobHelper.executeAndAwaitJob(JobName);
final Boolean isNPAAfter = (Boolean)
this.loanTransactionHelper.getLoanDetail(requestSpec, responseSpec, loanID,
"isNPA");
- Assertions.assertTrue(isNPAAfter);
+ assertTrue(isNPAAfter);
}
@Test
@@ -1090,6 +1164,21 @@ public class SchedulerJobsTestResults {
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
}
+ private Integer createLoanProductWithPeriodicAccrual(final String
chargeId) {
+ final Account assetAccount = this.accountHelper.createAssetAccount();
+ final Account assetFeeAndPenaltyAccount =
this.accountHelper.createAssetAccount();
+ final Account incomeAccount = this.accountHelper.createIncomeAccount();
+ final Account expenseAccount =
this.accountHelper.createExpenseAccount();
+ final Account overpaymentAccount =
this.accountHelper.createLiabilityAccount();
+
+ final String loanProductJSON = new
LoanProductTestBuilder().withPrincipal("15,000.00").withNumberOfRepayments("4")
+
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")
+ .withAccountingRulePeriodicAccrual(new Account[] {
assetAccount, incomeAccount, expenseAccount, overpaymentAccount })
+
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
+
.withFeeAndPenaltyAssetAccount(assetFeeAndPenaltyAccount).build(chargeId);
+ return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+ }
+
private void addCollaterals(List<HashMap> collaterals, Integer
collateralId, BigDecimal quantity) {
collaterals.add(collaterals(collateralId, quantity));
}