This is an automated email from the ASF dual-hosted git repository.

arnold 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 8954de5de Allow to add charge with same disbursement date
8954de5de is described below

commit 8954de5ded477ede5303d36c44dbe9ba2d4caeee
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Mon Oct 24 22:08:35 2022 -0500

    Allow to add charge with same disbursement date
---
 .../portfolio/loanaccount/domain/LoanCharge.java   |  11 ++
 .../domain/LoanRepaymentScheduleInstallment.java   |   4 +
 .../LoanRepaymentScheduleProcessingWrapper.java    |   4 +
 .../LoanChargeSpecificDueDateTest.java             | 148 +++++++++++++++++++++
 .../common/charges/ChargesHelper.java              |  10 ++
 .../common/loans/LoanTransactionHelper.java        |  25 ++++
 6 files changed, 202 insertions(+)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
index 302a2b461..294966ce1 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
@@ -712,6 +712,17 @@ public class LoanCharge extends AbstractPersistableCustom {
         return target != null && target.isAfter(fromNotInclusive) && 
!target.isAfter(upToAndInclusive);
     }
 
+    public boolean isDueForCollectionFromIncludingAndUpToAndIncluding(final 
LocalDate fromNotInclusive, final LocalDate upToAndInclusive) {
+        final LocalDate dueDate = getDueLocalDate();
+        return 
occursOnDayFromAndIncludingAndUpToAndIncluding(fromNotInclusive, 
upToAndInclusive, dueDate);
+    }
+
+    private boolean occursOnDayFromAndIncludingAndUpToAndIncluding(final 
LocalDate fromAndInclusive, final LocalDate upToAndInclusive,
+            final LocalDate target) {
+        return target != null && (target.isEqual(fromAndInclusive) || 
target.isAfter(fromAndInclusive))
+                && !target.isAfter(upToAndInclusive);
+    }
+
     public boolean isFeeCharge() {
         return !this.penaltyCharge;
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 47d57fc93..7aea6ea41 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -894,4 +894,8 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
         this.additional = true;
     }
 
+    public boolean isFirstPeriod() {
+        return (this.installmentNumber == 1);
+    }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
index 8a0fb8d10..72fec14af 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java
@@ -118,6 +118,10 @@ public class LoanRepaymentScheduleProcessingWrapper {
                     cumulative = cumulative.plus(loanChargeAmt);
                 } else if 
(loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
                     cumulative = cumulative.plus(loanCharge.amount());
+                    // Special case for Loan Charges (Due Date) added the same 
disbursement date
+                } else if (period.isFirstPeriod()
+                        && 
loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, 
periodEnd)) {
+                    cumulative = cumulative.plus(loanCharge.amount());
                 }
             }
         }
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
new file mode 100644
index 000000000..37c62dcdf
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java
@@ -0,0 +1,148 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.time.LocalDate;
+import java.util.HashMap;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostChargesResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
+import 
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@Slf4j
+public class LoanChargeSpecificDueDateTest {
+
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private LoanTransactionHelper loanTransactionHelper;
+    private static final String principalAmount = "1000.00";
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+
+        requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+
+        loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, 
this.responseSpec);
+    }
+
+    @Test
+    public void testApplyLoanSpecificDueDateChargeWithDisbursementDate() {
+
+        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 
= createLoanProduct(loanTransactionHelper, null);
+        assertNotNull(getLoanProductsProductResponse);
+
+        // Older date to have more than one overdue installment
+        LocalDate transactionDate = todaysDate;
+        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);
+
+        // Get loan details
+        GetLoansLoanIdResponse getLoansLoanIdResponse = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("0.00"));
+
+        // Apply Loan Charge with specific due date
+
+        final String feeAmount = "10.00";
+        String payloadJSON = 
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
 feeAmount, false);
+        final PostChargesResponse postChargesResponse = 
ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON);
+        assertNotNull(postChargesResponse);
+        final Integer loanChargeId = postChargesResponse.getResourceId();
+
+        payloadJSON = 
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(loanChargeId.toString(),
 operationDate, feeAmount);
+        PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse = 
loanTransactionHelper.addChargeForLoan(loanId, payloadJSON,
+                responseSpec);
+        assertNotNull(postLoansLoanIdChargesResponse);
+
+        // Get loan details expecting to have a delinquency classification
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("10.00"));
+
+        // Make a full repayment to close the Loan
+        Float amount = Float.valueOf("1010.00");
+        PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = 
loanTransactionHelper.makeLoanRepayment(operationDate, amount,
+                loanId);
+        assertNotNull(loanIdTransactionsResponse);
+        log.info("Loan Transaction Id: {} {}", loanId, 
loanIdTransactionsResponse.getResourceId());
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        assertNotNull(getLoansLoanIdResponse);
+        validateLoanAccount(getLoansLoanIdResponse, Double.valueOf("0.00"), 
Double.valueOf("0.00"));
+
+    }
+
+    private GetLoanProductsProductIdResponse createLoanProduct(final 
LoanTransactionHelper loanTransactionHelper,
+            final Integer delinquencyBucketId) {
+        final HashMap<String, Object> loanProductMap = new 
LoanProductTestBuilder().build(null, delinquencyBucketId);
+        final Integer loanProductId = 
loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
+        return loanTransactionHelper.getLoanProduct(loanProductId);
+    }
+
+    private Integer createLoanAccount(final LoanTransactionHelper 
loanTransactionHelper, final String clientId, final String loanProductId,
+            final String operationDate) {
+        final String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal(principalAmount).withLoanTermFrequency("12")
+                
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1")
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod("0") //
+                .withExpectedDisbursementDate(operationDate) //
+                .withInterestTypeAsDecliningBalance() //
+                .withSubmittedOnDate(operationDate) //
+                .build(clientId, loanProductId, null);
+        final Integer loanId = 
loanTransactionHelper.getLoanId(loanApplicationJSON);
+        loanTransactionHelper.approveLoan(operationDate, principalAmount, 
loanId, null);
+        
loanTransactionHelper.disburseLoanWithNetDisbursalAmount(operationDate, loanId, 
principalAmount);
+        return loanId;
+    }
+
+    private void validateLoanAccount(GetLoansLoanIdResponse 
getLoansLoanIdResponse, Double principal, Double fees) {
+        assertNotNull(getLoansLoanIdResponse);
+        loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
+        
loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse,
 principal);
+        
loanTransactionHelper.validateLoanFeesOustandingBalance(getLoansLoanIdResponse, 
fees);
+        
loanTransactionHelper.validateLoanTotalOustandingBalance(getLoansLoanIdResponse,
 (principal + fees));
+    }
+
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java
index 38119c95d..a10472e75 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/charges/ChargesHelper.java
@@ -23,6 +23,8 @@ import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
 import java.util.ArrayList;
 import java.util.HashMap;
+import org.apache.fineract.client.models.PostChargesResponse;
+import org.apache.fineract.client.util.JSON;
 import org.apache.fineract.integrationtests.common.CommonConstants;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
@@ -83,6 +85,8 @@ public final class ChargesHelper {
     public static final String FEE_ON_MONTH_DAY = "04 March";
     private static final String MONTH_DAY_FORMAT = "dd MMM";
 
+    private static final Gson GSON = new JSON().getGson();
+
     public static String getSavingsSpecifiedDueDateJSON() {
         final HashMap<String, Object> map = populateDefaultsForSavings();
         map.put("chargeTimeType", CHARGE_SPECIFIED_DUE_DATE);
@@ -385,6 +389,12 @@ public final class ChargesHelper {
         return Utils.performServerPost(requestSpec, responseSpec, 
CREATE_CHARGES_URL, request, "resourceId");
     }
 
+    public static PostChargesResponse createLoanCharge(final 
RequestSpecification requestSpec, final ResponseSpecification responseSpec,
+            final String payload) {
+        final String response = Utils.performServerPost(requestSpec, 
responseSpec, CREATE_CHARGES_URL, payload, null);
+        return GSON.fromJson(response, PostChargesResponse.class);
+    }
+
     public static ArrayList<HashMap> getCharges(final RequestSpecification 
requestSpec, final ResponseSpecification responseSpec) {
         return (ArrayList) Utils.performServerGet(requestSpec, responseSpec, 
CHARGES_URL + "?" + Utils.TENANT_IDENTIFIER, "");
     }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index e80419c36..1719595e1 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -48,6 +48,7 @@ import 
org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdSummary;
 import 
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
 import 
org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdResponse;
@@ -490,6 +491,14 @@ public class LoanTransactionHelper {
         return (Integer) response.get("resourceId");
     }
 
+    public PostLoansLoanIdChargesResponse addChargeForLoan(final Integer 
loanId, final String payload,
+            final ResponseSpecification responseSpecParam) {
+        log.info("--------------------------------- ADD CHARGES FOR LOAN 
--------------------------------");
+        final String ADD_CHARGES_URL = LOAN_ACCOUNT_URL + "/" + loanId + 
"/charges?" + Utils.TENANT_IDENTIFIER;
+        final String response = Utils.performServerPost(requestSpec, 
responseSpecParam, ADD_CHARGES_URL, payload);
+        return GSON.fromJson(response, PostLoansLoanIdChargesResponse.class);
+    }
+
     public Object addChargesForAllreadyDisursedLoan(final Integer loanId, 
final String request,
             final ResponseSpecification responseSpecification) {
         final String ADD_CHARGES_URL = "/fineract-provider/api/v1/loans/" + 
loanId + "/charges?" + Utils.TENANT_IDENTIFIER;
@@ -1160,4 +1169,20 @@ public class LoanTransactionHelper {
         }
     }
 
+    public void validateLoanFeesOustandingBalance(GetLoansLoanIdResponse 
getLoansLoanIdResponse, Double amountExpected) {
+        GetLoansLoanIdSummary getLoansLoanIdSummary = 
getLoansLoanIdResponse.getSummary();
+        if (getLoansLoanIdSummary != null) {
+            log.info("Loan with Fees Outstanding Balance {} expected {}", 
getLoansLoanIdSummary.getFeeChargesOutstanding(), amountExpected);
+            assertEquals(amountExpected, 
getLoansLoanIdSummary.getFeeChargesOutstanding());
+        }
+    }
+
+    public void validateLoanTotalOustandingBalance(GetLoansLoanIdResponse 
getLoansLoanIdResponse, Double amountExpected) {
+        GetLoansLoanIdSummary getLoansLoanIdSummary = 
getLoansLoanIdResponse.getSummary();
+        if (getLoansLoanIdSummary != null) {
+            log.info("Loan with Total Outstanding Balance {} expected {}", 
getLoansLoanIdSummary.getTotalOutstanding(), amountExpected);
+            assertEquals(amountExpected, 
getLoansLoanIdSummary.getTotalOutstanding());
+        }
+    }
+
 }

Reply via email to