FINERACT-2081: Loan account data additional fields for summary and delinquency

This commit is contained in:
Jose Alberto Hernandez
2024-07-11 17:41:57 +02:00
committed by Adam Saghy
parent 4ac102f4e0
commit 79f32d32a3
11 changed files with 371 additions and 34 deletions
@@ -70,6 +70,7 @@ import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
import org.apache.fineract.integrationtests.common.accounting.PeriodicAccrualAccountingHelper;
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
@@ -4601,6 +4602,85 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
});
}
// UC144: Advanced payment allocation with Interest,
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
// 1. Create a Loan product with Adv. Pment. Alloc. and with 5% Interest, 360/30, 1 repayment per month
// 2. Submit Loan and approve
// 3. Disburse
// 4. Validate Repayment Schedule
@Test
public void uc144() {
final String operationDate = "1 January 2024";
AtomicLong createdLoanId = new AtomicLong();
runAt(operationDate, () -> {
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
.daysInMonthType(DaysInMonthType.ACTUAL.getValue()).daysInYearType(DaysInYearType.DAYS_365.getValue())
.numberOfRepayments(4)//
.repaymentEvery(1)//
.repaymentFrequencyType(2L)//
.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, 100.0, 5);
applicationRequest = applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
.interestRatePerPeriod(BigDecimal.valueOf(12.3)).interestCalculationPeriodType(DAYS)
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(1)
.repaymentFrequencyType(2);
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
createdLoanId.set(loanResponse.getLoanId());
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()
.approvedLoanAmount(BigDecimal.valueOf(100)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
// After Disbursement we are expecting no Accrual transactions
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
assertEquals(0.0, loanDetails.getSummary().getTotalUnpaidAccruedDueInterest());
assertEquals(0.0, loanDetails.getSummary().getTotalUnpaidAccruedNotDueInterest());
});
// Not Due yet
runAt("30 January 2024", () -> {
// Generate the Accruals
final PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(requestSpec,
responseSpec);
periodicAccrualAccountingHelper.runPeriodicAccrualAccounting("30 January 2024");
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
assertEquals(0.0, loanDetails.getSummary().getTotalUnpaidAccruedDueInterest());
assertEquals(0.97, loanDetails.getSummary().getTotalUnpaidAccruedNotDueInterest());
// Partial interest repayment
addRepaymentForLoan(createdLoanId.get(), 20.50, "30 January 2024");
loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
assertEquals(0.0, loanDetails.getSummary().getTotalUnpaidAccruedDueInterest());
assertEquals(0.05, loanDetails.getSummary().getTotalUnpaidAccruedNotDueInterest());
});
// Not Due and Due Interest
runAt("20 February 2024", () -> {
final PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(requestSpec,
responseSpec);
periodicAccrualAccountingHelper.runPeriodicAccrualAccounting("20 February 2024");
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
assertEquals(0.12, loanDetails.getSummary().getTotalUnpaidAccruedDueInterest());
assertEquals(0.52, loanDetails.getSummary().getTotalUnpaidAccruedNotDueInterest());
});
}
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.integrationtests;
import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.RepaymentFrequencyType.DAYS;
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.PAUSE;
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -55,7 +56,11 @@ import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
import org.apache.fineract.client.models.PostLoansDelinquencyActionResponse;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.PutDelinquencyBucketResponse;
import org.apache.fineract.client.models.PutDelinquencyRangeResponse;
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
@@ -1444,6 +1449,115 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
}
}
@Test
public void testLoanDelinquencyDataWithAmountPerPortions() {
// Given
final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
ArrayList<Integer> rangeIds = new ArrayList<>();
// First Range
String jsonRange = DelinquencyRangesHelper.getAsJSON(4, 30);
PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
delinquencyRangeResponse.getResourceId());
String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
responseSpec, jsonBucket);
assertNotNull(delinquencyBucketResponse);
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
delinquencyBucketResponse.getResourceId());
// Client and Loan account creation
final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
Math.toIntExact(delinquencyBucket.getId()), null);
assertNotNull(getLoanProductsProductResponse);
log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
final LocalDate todaysDate = Utils.getLocalDateOfTenant();
log.info("Local date of Tenant: {}", todaysDate);
// Older date to have more than one overdue installment
final LocalDate transactionDate = todaysDate.minusDays(50);
final String operationDate = Utils.dateFormatter.format(transactionDate);
final Double amount = 2000.0;
PostLoansRequest applicationRequest = applyLoanRequest(clientId, getLoanProductsProductResponse.getId(), operationDate, amount, 4);
applicationRequest = applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
.interestRatePerPeriod(BigDecimal.valueOf(12.3)).interestCalculationPeriodType(DAYS).repaymentEvery(1)
.repaymentFrequencyType(2);
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
final Long loanId = loanResponse.getResourceId();
loanTransactionHelper.approveLoan(loanId, new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(amount))
.dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
loanTransactionHelper.disburseLoan(loanId, new PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(amount)).locale("en"));
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
log.info("Loan Delinquency Range after Disbursement {}", loanDetails.getDelinquencyRange().getClassification());
assertNotNull(loanDetails.getDelinquent());
log.info("Loan Delinquency Data {} {}", loanDetails.getDelinquent().getDelinquentPrincipal(),
loanDetails.getDelinquent().getDelinquentInterest());
assertNotNull(loanDetails.getDelinquent().getDelinquentPrincipal());
assertEquals(305.91, loanDetails.getDelinquent().getDelinquentPrincipal());
assertNotNull(loanDetails.getDelinquent().getDelinquentInterest());
assertEquals(250.72, loanDetails.getDelinquent().getDelinquentInterest());
// Apply a partial repayment to move only the interest
PostLoansLoanIdTransactionsResponse loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 120f,
loanId.intValue());
assertNotNull(loansLoanIdTransactions);
log.info("Loan repayment transaction id {}", loansLoanIdTransactions.getResourceId());
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
assertNotNull(loanDetails.getDelinquent());
assertNotNull(loanDetails.getDelinquencyRange().getClassification());
assertEquals(305.91, loanDetails.getDelinquent().getDelinquentPrincipal());
assertEquals(130.72, loanDetails.getDelinquent().getDelinquentInterest());
// Apply a repayment to cover interest and part of the principal
loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 330.72f, loanId.intValue());
assertNotNull(loansLoanIdTransactions);
log.info("Loan repayment transaction id {}", loansLoanIdTransactions.getResourceId());
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
assertNotNull(loanDetails.getDelinquent());
assertNotNull(loanDetails.getDelinquencyRange().getClassification());
assertEquals(105.91, loanDetails.getDelinquent().getDelinquentPrincipal());
assertEquals(0.0, loanDetails.getDelinquent().getDelinquentInterest());
// Apply a repayment to cover the remain principal
loansLoanIdTransactions = loanTransactionHelper.makeLoanRepayment(operationDate, 105.91f, loanId.intValue());
assertNotNull(loansLoanIdTransactions);
log.info("Loan repayment transaction id {}", loansLoanIdTransactions.getResourceId());
// Loan without Delinquency Classification
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
assertNotNull(loanDetails.getDelinquent());
assertNull(loanDetails.getDelinquencyRange());
assertEquals(0.0, loanDetails.getDelinquent().getDelinquentPrincipal());
assertEquals(0.0, loanDetails.getDelinquent().getDelinquentInterest());
// Undo the last repayment transaction we must to have pending the principal
PostLoansLoanIdTransactionsResponse reverseRepayment = loanTransactionHelper.reverseLoanTransaction(loanId,
loansLoanIdTransactions.getResourceId(), new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM yyyy")
.transactionDate(operationDate).transactionAmount(0.0).locale("en"));
assertNotNull(reverseRepayment);
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
assertNotNull(loanDetails.getDelinquent());
assertNotNull(loanDetails.getDelinquencyRange().getClassification());
assertEquals(105.91, loanDetails.getDelinquent().getDelinquentPrincipal());
assertEquals(0.0, loanDetails.getDelinquent().getDelinquentInterest());
}
private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
final Integer delinquencyBucketId, final String inArrearsTolerance) {
final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().withInArrearsTolerance(inArrearsTolerance).build(null,