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 ea172db71 FINERACT-1954: Waive null pointer and due date fix - [x] Fix 
Waive due date handle - [x] Fix null pointer exception - [x] Integration test
ea172db71 is described below

commit ea172db712ea63f1f23e9b6f4f5ba90967d719bd
Author: Janos Haber <[email protected]>
AuthorDate: Fri Jul 21 15:56:07 2023 +0200

    FINERACT-1954: Waive null pointer and due date fix
    - [x] Fix Waive due date handle
    - [x] Fix null pointer exception
    - [x] Integration test
---
 .../portfolio/loanaccount/domain/Loan.java         |   7 +-
 .../loanaccount/domain/LoanInstallmentCharge.java  |   3 +-
 ...tLoanRepaymentScheduleTransactionProcessor.java |   1 -
 .../LoanChargeSpecificDueDateTest.java             | 151 +++++++++++++++++++++
 .../common/charges/ChargesHelper.java              |  23 ++++
 .../common/loans/LoanTransactionHelper.java        |  11 ++
 6 files changed, 193 insertions(+), 3 deletions(-)

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 9c62a71a1..7d78c4a1a 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
@@ -1081,7 +1081,12 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
                 transactionDate = loanCharge.getDueLocalDate();
             }
         } else if (loanCharge.isInstalmentFee()) {
-            transactionDate = 
loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getRepaymentInstallment().getDueDate();
+            LocalDate repaymentDueDate = 
loanCharge.getInstallmentLoanCharge(loanInstallmentNumber).getRepaymentInstallment().getDueDate();
+            if (repaymentDueDate.isAfter(DateUtils.getBusinessLocalDate())) {
+                transactionDate = DateUtils.getBusinessLocalDate();
+            } else {
+                transactionDate = repaymentDueDate;
+            }
         }
 
         scheduleGeneratorDTO.setRecalculateFrom(transactionDate);
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
index 1ed06d2a6..5637d5f59 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
@@ -25,6 +25,7 @@ import jakarta.persistence.ManyToOne;
 import jakarta.persistence.Table;
 import java.math.BigDecimal;
 import 
org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.interoperation.util.MathUtil;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import 
org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
@@ -168,7 +169,7 @@ public class LoanInstallmentCharge extends 
AbstractPersistableCustom implements
     }
 
     public boolean isPending() {
-        return !(isPaid() || isWaived());
+        return !MathUtil.isZero(this.amountOutstanding);
     }
 
     public boolean isChargeAmountpaid(MonetaryCurrency currency) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index 5be6b1f75..039da3468 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -209,7 +209,6 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
             }
         }
         reprocessInstallments(installments, currency);
-
         return changedTransactionDetail;
     }
 
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 1b3695ed3..442354da0 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
@@ -20,6 +20,7 @@ package org.apache.fineract.integrationtests;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import io.restassured.builder.RequestSpecBuilder;
 import io.restassured.builder.ResponseSpecBuilder;
@@ -30,10 +31,12 @@ import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Optional;
 import lombok.extern.slf4j.Slf4j;
 import 
org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
 import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
 import org.apache.fineract.client.models.JournalEntryTransactionItem;
 import org.apache.fineract.client.models.PostChargesResponse;
 import 
org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
@@ -233,6 +236,154 @@ public class LoanChargeSpecificDueDateTest {
 
     }
 
+    @Test
+    public void testApplyAndWaiveInstallmentFee() {
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+        final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, todaysDate);
+
+        // 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, "1", "0");
+
+        // Get loan details
+        GetLoansLoanIdResponse getLoansLoanIdResponse = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("0.00"), false);
+
+        // Apply Loan Charge with specific due date
+        final String feeAmount = "1.500000";
+        String payloadJSON = ChargesHelper.getLoanSpecificInstallmentFeeJSON();
+        final PostChargesResponse postChargesResponse = 
ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON);
+        assertNotNull(postChargesResponse);
+        final Long chargeId = postChargesResponse.getResourceId();
+        assertNotNull(chargeId);
+
+        float amount = Float.parseFloat("5.00");
+        PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = 
loanTransactionHelper.makeLoanRepayment(operationDate, amount,
+                loanId);
+
+        payloadJSON = 
LoanTransactionHelper.getSpecifiedInstallmentChargesForLoanAsJSON(chargeId.toString(),
 feeAmount);
+        PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse = 
loanTransactionHelper.addChargeForLoan(loanId, payloadJSON,
+                responseSpec);
+        assertNotNull(postLoansLoanIdChargesResponse);
+        final Long loanChargeId = 
postLoansLoanIdChargesResponse.getResourceId();
+        assertNotNull(loanChargeId);
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("10.00"), false);
+
+        // Waive the Loan Charge
+        final PostLoansLoanIdChargesChargeIdResponse 
postWaiveLoanChargesResponse = 
loanTransactionHelper.applyLoanChargeCommand(loanId,
+                loanChargeId, "waive", Utils.emptyJson());
+        assertNotNull(postWaiveLoanChargesResponse);
+
+        // evaluate the outstanding
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("0.00"), false);
+
+        Optional<GetLoansLoanIdTransactions> waiveTransaction = 
getLoansLoanIdResponse.getTransactions().stream()
+                .filter(transaction -> transaction.getType().getWaiveCharges() 
!= null && transaction.getType().getWaiveCharges())
+                .findFirst();
+        assertTrue(waiveTransaction.isPresent());
+        assertEquals(transactionDate, waiveTransaction.get().getDate());
+
+        // Make a full repayment to close the Loan
+        amount = Float.parseFloat("1000.00");
+        loanIdTransactionsResponse = 
loanTransactionHelper.makeLoanRepayment(operationDate, amount, loanId);
+        assertNotNull(loanIdTransactionsResponse);
+        log.info("Loan Transaction Id: {} {}", loanId, 
loanIdTransactionsResponse.getResourceId());
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        assertNotNull(getLoansLoanIdResponse);
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, 
"loanStatusType.closed.obligations.met");
+
+    }
+
+    @Test
+    public void testApplyAndWaiveInstallmentFeeAnotherDueDate() {
+        GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, 
responseSpec, Boolean.TRUE);
+        final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, todaysDate);
+
+        // 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, "1", "0");
+
+        // Get loan details
+        GetLoansLoanIdResponse getLoansLoanIdResponse = 
loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("0.00"), false);
+
+        // Apply Loan Charge with specific due date
+        final String feeAmount = "1.500000";
+        String payloadJSON = ChargesHelper.getLoanSpecificInstallmentFeeJSON();
+        final PostChargesResponse postChargesResponse = 
ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON);
+        assertNotNull(postChargesResponse);
+        final Long chargeId = postChargesResponse.getResourceId();
+        assertNotNull(chargeId);
+
+        float amount = Float.parseFloat("5.00");
+        PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = 
loanTransactionHelper.makeLoanRepayment(operationDate, amount,
+                loanId);
+        transactionDate = todaysDate.plusDays(32);
+        BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, transactionDate);
+
+        payloadJSON = 
LoanTransactionHelper.getSpecifiedInstallmentChargesForLoanAsJSON(chargeId.toString(),
 feeAmount);
+        PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse = 
loanTransactionHelper.addChargeForLoan(loanId, payloadJSON,
+                responseSpec);
+        assertNotNull(postLoansLoanIdChargesResponse);
+        final Long loanChargeId = 
postLoansLoanIdChargesResponse.getResourceId();
+        assertNotNull(loanChargeId);
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("10.00"), false);
+        LocalDate repaymentDueDate = 
getLoansLoanIdResponse.getRepaymentSchedule().getPeriods().get(1).getDueDate();
+        // Waive the Loan Charge
+        final PostLoansLoanIdChargesChargeIdResponse 
postWaiveLoanChargesResponse = 
loanTransactionHelper.applyLoanChargeCommand(loanId,
+                loanChargeId, "waive", Utils.emptyJson());
+        assertNotNull(postWaiveLoanChargesResponse);
+
+        // evaluate the outstanding
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        validateLoanAccount(getLoansLoanIdResponse, 
Double.valueOf(principalAmount), Double.valueOf("0.00"), false);
+
+        Optional<GetLoansLoanIdTransactions> waiveTransaction = 
getLoansLoanIdResponse.getTransactions().stream()
+                .filter(transaction -> transaction.getType().getWaiveCharges() 
!= null && transaction.getType().getWaiveCharges())
+                .findFirst();
+        assertTrue(waiveTransaction.isPresent());
+        assertEquals(repaymentDueDate, waiveTransaction.get().getDate());
+
+        // Make a full repayment to close the Loan
+        amount = Float.parseFloat("1000.00");
+        loanIdTransactionsResponse = 
loanTransactionHelper.makeLoanRepayment(operationDate, amount, loanId);
+        assertNotNull(loanIdTransactionsResponse);
+        log.info("Loan Transaction Id: {} {}", loanId, 
loanIdTransactionsResponse.getResourceId());
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, 
responseSpec, loanId);
+        assertNotNull(getLoansLoanIdResponse);
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, 
"loanStatusType.closed.obligations.met");
+
+    }
+
     @Test
     public void 
testApplyAndWaiveLoanSpecificDueDatePenaltyWithDisbursementDate() {
 
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 3d1013a90..d554d3b02 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
@@ -237,6 +237,19 @@ public final class ChargesHelper extends IntegrationTest {
         return chargesCreateJson;
     }
 
+    public static String getLoanSpecificInstallmentFeeJSON() {
+        final HashMap<String, Object> map = populateDefaultsForLoan();
+        map.put("chargeTimeType", CHARGE_INSTALLMENT_FEE);
+        map.put("chargePaymentMode", CHARGE_PAYMENT_MODE_REGULAR);
+        map.put("penalty", false);
+        map.put("amount", "1.500000");
+        map.put("chargeCalculationType", 
CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST);
+
+        String chargesCreateJson = new Gson().toJson(map);
+        LOG.info("{}", chargesCreateJson);
+        return chargesCreateJson;
+    }
+
     public static String getLoanSpecifiedDueDateWithAccountTransferJSON(final 
Integer chargeCalculationType, final String amount,
             boolean penalty) {
         return getLoanSpecifiedDueDateJSON(chargeCalculationType, amount, 
penalty, ChargesHelper.CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER);
@@ -251,6 +264,16 @@ public final class ChargesHelper extends IntegrationTest {
         return getLoanInstallmentJSON(chargeCalculationType, amount, penalty, 
ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
     }
 
+    public static String getLoanOverdueInstallmentJSON(final String amount) {
+        final HashMap<String, Object> map = populateDefaultsForLoan();
+        map.put("chargeId", CHARGE_OVERDUE_INSTALLMENT_FEE);
+        map.put("amount", amount);
+
+        String chargesCreateJson = new Gson().toJson(map);
+        LOG.info("{}", chargesCreateJson);
+        return chargesCreateJson;
+    }
+
     public static String getLoanInstallmentJSON(final Integer 
chargeCalculationType, final String amount, final boolean penalty,
             final Integer paymentMode) {
         final HashMap<String, Object> map = populateDefaultsForLoan();
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 46fa15454..8a774dcc1 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
@@ -1206,6 +1206,17 @@ public class LoanTransactionHelper extends 
IntegrationTest {
         return json;
     }
 
+    public static String getSpecifiedInstallmentChargesForLoanAsJSON(final 
String chargeId, final String amount) {
+        final HashMap<String, String> map = new HashMap<>();
+        map.put("locale", "en_GB");
+        map.put("dateFormat", "dd MMMM yyyy");
+        map.put("amount", amount);
+        map.put("chargeId", chargeId);
+        String json = new Gson().toJson(map);
+        log.info("{}", json);
+        return json;
+    }
+
     public static String getDisbursementChargesForLoanAsJSON(final String 
chargeId) {
         return getDisbursementChargesForLoanAsJSON(chargeId, "100");
     }

Reply via email to