FINERACT-1724: Create accrual entry for specific due date charge even on disbursement date

This commit is contained in:
Adam Saghy
2023-03-13 11:19:45 +01:00
parent 9baf053d0b
commit 5ced30b864
4 changed files with 141 additions and 10 deletions
@@ -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());
@@ -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 ");
@@ -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,
@@ -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));
}