From 0458643d8f2864ee0b365253a4196d680b15dc4a Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Wed, 10 May 2023 00:08:47 +0200 Subject: [PATCH] FINERACT-1724: Periodic accrual fix for charge on disbursement date --- .../portfolio/loanaccount/domain/Loan.java | 9 ++- .../LoanChargeSpecificDueDateTest.java | 70 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index ecef2f2a4..3c7439b5f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -1323,12 +1323,17 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { private void applyPeriodicAccruals(final Collection accruals) { List installments = getRepaymentScheduleInstallments(); for (LoanRepaymentScheduleInstallment installment : installments) { + Money interest = Money.zero(getCurrency()); Money fee = Money.zero(getCurrency()); Money penality = Money.zero(getCurrency()); for (LoanTransaction loanTransaction : accruals) { - if (loanTransaction.getTransactionDate().isAfter(installment.getFromDate()) - && !loanTransaction.getTransactionDate().isAfter(installment.getDueDate())) { + boolean isInRange = installment.isFirstPeriod() + ? !loanTransaction.getTransactionDate().isBefore(installment.getFromDate()) + && !loanTransaction.getTransactionDate().isAfter(installment.getDueDate()) + : loanTransaction.getTransactionDate().isAfter(installment.getFromDate()) + && !loanTransaction.getTransactionDate().isAfter(installment.getDueDate()); + if (isInRange) { interest = interest.plus(loanTransaction.getInterestPortion(getCurrency())); fee = fee.plus(loanTransaction.getFeeChargesPortion(getCurrency())); penality = penality.plus(loanTransaction.getPenaltyChargesPortion(getCurrency())); 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 7e942b3fb..1b3695ed3 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 @@ -545,6 +545,8 @@ public class LoanChargeSpecificDueDateTest { responseSpec); assertNotNull(postLoansLoanIdChargesResponse); + periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(operationDate); + // Get loan details expecting to have a delinquency classification getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("10.00"), true); @@ -565,6 +567,74 @@ public class LoanChargeSpecificDueDateTest { validateLoanAccount(getLoansLoanIdResponse, Double.parseDouble(principalAmount) * 2, Double.valueOf("20.00"), true); } + @Test + public void testApplyLoanSpecificDueDatePenaltyAccrualWithDisbursementDateWithMultipleDisbursement() { + + final LocalDate todaysDate = Utils.getLocalDateOfTenant(); + + // Client and Loan account creation + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012"); + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithPeriodicAccrual(loanTransactionHelper, + null); + assertNotNull(getLoanProductsProductResponse); + + // Older date to have more than one overdue installment + LocalDate transactionDate = todaysDate.minusDays(2); + String operationDate = Utils.dateFormatter.format(transactionDate); + log.info("Operation date {}", transactionDate); + + // Create Loan Account + final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), + getLoanProductsProductResponse.getId().toString(), operationDate, "12", "0"); + + // Get loan details + GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("0.00"), true); + + // Apply Loan Charge with specific due date + + final String feeAmount = "10.00"; + String payloadJSON = ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, feeAmount, true); + final PostChargesResponse postChargesResponse = ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON); + assertNotNull(postChargesResponse); + final Long loanChargeId = postChargesResponse.getResourceId(); + assertNotNull(loanChargeId); + + payloadJSON = LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(loanChargeId.toString(), operationDate, feeAmount); + PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse = loanTransactionHelper.addChargeForLoan(loanId, payloadJSON, + responseSpec); + assertNotNull(postLoansLoanIdChargesResponse); + + periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(operationDate); + + // Get loan details expecting to have a delinquency classification + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("10.00"), true); + + transactionDate = transactionDate.plusDays(1); + operationDate = Utils.dateFormatter.format(transactionDate); + + loanTransactionHelper.disburseLoan((long) loanId, new PostLoansLoanIdRequest().actualDisbursementDate(operationDate) + .transactionAmount(new BigDecimal("1000")).locale("en").dateFormat("dd MMMM yyyy")); + + // Get loan details expecting to have a delinquency classification + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, Double.parseDouble(principalAmount) * 2, Double.valueOf("10.00"), true); + + periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(operationDate); + + operationDate = Utils.dateFormatter.format(transactionDate.plusMonths(1)); + payloadJSON = LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(loanChargeId.toString(), operationDate, feeAmount); + postLoansLoanIdChargesResponse = loanTransactionHelper.addChargeForLoan(loanId, payloadJSON, responseSpec); + + transactionDate = transactionDate.plusDays(1); + operationDate = Utils.dateFormatter.format(transactionDate); + periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(operationDate); + // Get loan details expecting to have a delinquency classification + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, Double.parseDouble(principalAmount) * 2, Double.valueOf("20.00"), true); + } + private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper, final Integer delinquencyBucketId) { final HashMap loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId);