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());
+ }
+ }
+
}