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 ec1c55d02 FINERACT-2081: fix disburse error scenario
ec1c55d02 is described below

commit ec1c55d022c0020d5810848238bfc6c75e43f9f8
Author: Kristof Jozsa <[email protected]>
AuthorDate: Thu Aug 1 17:02:32 2024 +0200

    FINERACT-2081: fix disburse error scenario
---
 .../fineract/test/helper/ErrorMessageHelper.java       |  4 ++++
 .../apache/fineract/test/stepdef/loan/LoanStepDef.java | 17 +++++++++++++++++
 .../src/test/resources/features/Loan.feature           | 14 ++++++++++++++
 .../fineract/portfolio/loanaccount/domain/Loan.java    | 18 ++----------------
 .../serialization/LoanTransactionValidator.java        | 17 +++++++++++++++++
 .../LoanWritePlatformServiceJpaRepositoryImpl.java     |  4 ++--
 .../LoanAccountBackdatedDisbursementTest.java          |  2 --
 7 files changed, 56 insertions(+), 20 deletions(-)

diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
index 9b64527ec..357db71a4 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java
@@ -66,6 +66,10 @@ public final class ErrorMessageHelper {
         return "Loan disbursal amount can't be greater than maximum applied 
loan amount calculation. Total disbursed amount: [0-9]*  Maximum disbursal 
amount: [0-9]*";
     }
 
+    public static String disbursePastDateFailure(Integer loanId, String 
actualDisbursementDate) {
+        return String.format("The date on which a loan is disbursed cannot be 
before its approval date: %s", actualDisbursementDate);
+    }
+
     public static String loanSubmitDateInFutureFailureMsg() {
         return "The date on which a loan is submitted cannot be in the 
future.";
     }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
index 81b48e523..2ab4058a1 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
@@ -1892,6 +1892,23 @@ public class LoanStepDef extends AbstractStepDef {
         
assertThat(fixedLengthactual).as(ErrorMessageHelper.wrongfixedLength(fixedLengthactual,
 fieldValue)).isEqualTo(fieldValue);
     }
 
+    @Then("Admin fails to disburse the loan on {string} with {string} EUR 
transaction amount because disbursement date is earlier than {string}")
+    public void disburseLoanFailureWithPastDate(String actualDisbursementDate, 
String transactionAmount, String futureApproveDate)
+            throws IOException {
+        Response<PostLoansResponse> loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        long loanId = loanResponse.body().getLoanId();
+        PostLoansLoanIdRequest disburseRequest = 
LoanRequestFactory.defaultLoanDisburseRequest()
+                
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new 
BigDecimal(transactionAmount));
+
+        String futureApproveDateISO = 
FORMATTER_EVENTS.format(FORMATTER.parse(futureApproveDate));
+        Response<PostLoansLoanIdResponse> loanDisburseResponse = 
loansApi.stateTransitions(loanId, disburseRequest, "disburse").execute();
+        testContext().set(TestContextKey.LOAN_DISBURSE_RESPONSE, 
loanDisburseResponse);
+        ErrorResponse errorDetails = ErrorResponse.from(loanDisburseResponse);
+        
assertThat(errorDetails.getHttpStatusCode()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(403);
+        assertThat(errorDetails.getSingleError().getDeveloperMessage())
+                .isEqualTo(ErrorMessageHelper.disbursePastDateFailure((int) 
loanId, futureApproveDateISO));
+    }
+
     private LoanStatusEnumDataV1 getExpectedStatus(String loanStatus) {
         LoanStatusEnumDataV1 result = new LoanStatusEnumDataV1();
         switch (loanStatus) {
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature 
b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
index 48d32ce89..60555c9b0 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
@@ -5508,3 +5508,17 @@ Feature: Loan
     And Admin successfully approves the loan on "01 February 2024" with "1000" 
amount and expected disbursement date on "01 February 2024"
     When Admin successfully disburse the loan on "01 February 2024" with 
"1000" EUR transaction amount
     Then LoanDetails has fixedLength field with int value: 60
+
+
+  Scenario: Actual disbursement date is in the past with advanced payment 
allocation product + submitted on date repaymentStartDateType
+    When Admin sets repaymentStartDateType for 
"LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to 
"SUBMITTED_ON_DATE"
+    When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan 
product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment 
allocation rule
+    When Admin sets the business date to "01 January 2023"
+    And Admin creates a client with random data
+    When Admin creates a fully customized loan with the following data:
+      | LoanProduct                                       | submitted on date 
| with Principal | ANNUAL interest rate % | interest type | interest 
calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023   | 
500            | 0                      | FLAT          | 
SAME_AS_REPAYMENT_PERIOD    | EQUAL_INSTALLMENTS | 45                | DAYS     
             | 15             | DAYS                   | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2023" with "500" 
amount and expected disbursement date on "01 January 2023"
+    Then Loan status has changed to "Approved"
+    Then Admin fails to disburse the loan on "31 December 2022" with "500" EUR 
transaction amount because disbursement date is earlier than "01 January 2023"
+    When Admin sets repaymentStartDateType for 
"LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to 
"DISBURSEMENT_DATE"
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index aff66589f..e6c59a5d7 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -1727,19 +1727,13 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom<Long> {
                 .collect(Collectors.toList());
     }
 
-    public boolean canDisburse(final LocalDate actualDisbursementDate) {
-        LocalDate loanSubmittedOnDate = this.submittedOnDate;
+    public boolean canDisburse() {
         final LoanStatus statusEnum = 
this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSED, this);
 
         boolean isMultiTrancheDisburse = false;
         LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus);
         if ((actualLoanStatus.isActive() || 
actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid())
                 && isAllTranchesNotDisbursed()) {
-            if (DateUtils.isBefore(actualDisbursementDate, 
loanSubmittedOnDate)) {
-                final String errorMsg = "Loan can't be disbursed before " + 
loanSubmittedOnDate;
-                throw new LoanDisbursalException(errorMsg, 
"actualdisbursementdate.before.submittedDate", loanSubmittedOnDate,
-                        actualDisbursementDate);
-            }
             isMultiTrancheDisburse = true;
         }
         return !statusEnum.hasStateOf(actualLoanStatus) || 
isMultiTrancheDisburse;
@@ -2012,18 +2006,10 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom<Long> {
             updateLoanOutstandingBalances();
         }
 
-        if (getApprovedOnDate() != null && DateUtils.isBefore(disbursedOn, 
getApprovedOnDate())) {
-            final String errorMessage = "The date on which a loan is disbursed 
cannot be before its approval date: "
-                    + getApprovedOnDate().toString();
-            throw new InvalidLoanStateTransitionException("disbursal", 
"cannot.be.before.approval.date", errorMessage, disbursedOn,
-                    getApprovedOnDate());
-        }
-
         LocalDate expectedDate = getExpectedFirstRepaymentOnDate();
         if (expectedDate != null && (DateUtils.isAfter(disbursedOn, 
this.fetchRepaymentScheduleInstallment(1).getDueDate())
                 || DateUtils.isAfter(disbursedOn, expectedDate)) && 
DateUtils.isEqual(disbursedOn, this.actualDisbursementDate)) {
-            final String errorMessage = "submittedOnDate cannot be after the 
loans  expectedFirstRepaymentOnDate: "
-                    + expectedDate.toString();
+            final String errorMessage = "submittedOnDate cannot be after the 
loans  expectedFirstRepaymentOnDate: " + expectedDate;
             throw new InvalidLoanStateTransitionException("disbursal", 
"cannot.be.after.expected.first.repayment.date", errorMessage,
                     disbursedOn, expectedDate);
         }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
index feb506855..de1f97922 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
@@ -80,6 +80,7 @@ import 
org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException
 import 
org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanChargeRefundException;
+import 
org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.LoanRepaymentScheduleNotFoundException;
 import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
@@ -213,6 +214,22 @@ public final class LoanTransactionValidator {
                         loan.getExpectedDisbursedOnLocalDate());
             }
 
+            if ((loan.getStatus().isActive() || 
loan.getStatus().isClosedObligationsMet() || loan.getStatus().isOverpaid())
+                    && loan.isAllTranchesNotDisbursed()) {
+                LocalDate submittedOnDate = loan.getSubmittedOnDate();
+                if (DateUtils.isBefore(actualDisbursementDate, 
submittedOnDate)) {
+                    final String errorMsg = "Loan can't be disbursed before " 
+ submittedOnDate;
+                    throw new LoanDisbursalException(errorMsg, 
"actualdisbursementdate.before.submittedDate", submittedOnDate,
+                            actualDisbursementDate);
+                }
+            }
+
+            LocalDate approvedOnDate = loan.getApprovedOnDate();
+            if (DateUtils.isBefore(actualDisbursementDate, approvedOnDate)) {
+                final String errorMessage = "The date on which a loan is 
disbursed cannot be before its approval date: " + approvedOnDate;
+                throw new InvalidLoanStateTransitionException("disbursal", 
"cannot.be.before.approval.date", errorMessage,
+                        actualDisbursementDate, approvedOnDate);
+            }
         });
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 51f1d04f2..823a99201 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -349,7 +349,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         final Locale locale = command.extractLocale();
         final DateTimeFormatter fmt = 
DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
 
-        if (loan.canDisburse(actualDisbursementDate)) {
+        if (loan.canDisburse()) {
             // Get netDisbursalAmount from disbursal screen field.
             final BigDecimal netDisbursalAmount = command
                     
.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName);
@@ -774,7 +774,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             // disbursement and actual disbursement happens on same date
             loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
             updateLoanCounters(loan, actualDisbursementDate);
-            boolean canDisburse = loan.canDisburse(actualDisbursementDate);
+            boolean canDisburse = loan.canDisburse();
             ChangedTransactionDetail changedTransactionDetail = null;
             if (canDisburse) {
                 Money amountBeforeAdjust = loan.getPrincipal();
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountBackdatedDisbursementTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountBackdatedDisbursementTest.java
index 1c5da42c2..e367b68c1 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountBackdatedDisbursementTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountBackdatedDisbursementTest.java
@@ -765,7 +765,6 @@ public class LoanAccountBackdatedDisbursementTest {
     @Test
     public void 
loanAccountBackDatedDisbursementWithDisbursementDateBeforeLoanSubmittedOnDateValidationTest()
 {
         try {
-
             final ResponseSpecification errorResponse = new 
ResponseSpecBuilder().expectStatusCode(403).build();
             final LoanTransactionHelper validationErrorHelper = new 
LoanTransactionHelper(this.requestSpec, errorResponse);
 
@@ -867,7 +866,6 @@ public class LoanAccountBackdatedDisbursementTest {
         } finally {
             GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.FALSE);
         }
-
     }
 
     @Test

Reply via email to