You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2023/01/31 15:43:10 UTC

[fineract] branch develop updated: FINERACT-1854: Undo last disbursal function deletes all disbursals on the last date

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 226a4321f FINERACT-1854: Undo last disbursal function deletes all disbursals on the last date
226a4321f is described below

commit 226a4321fb4b8907fbd4d852e6dbde64e1b3b501
Author: Jose Alberto Hernandez <al...@MacBook-Pro.local>
AuthorDate: Sun Jan 15 14:19:42 2023 -0600

    FINERACT-1854: Undo last disbursal function deletes all disbursals on the last date
---
 .../portfolio/loanaccount/domain/Loan.java         |  74 ++++-----
 .../LoanApplicationUndoLastTrancheTest.java        | 169 +++++++++++++++++++--
 .../common/loans/LoanTransactionHelper.java        |  13 ++
 3 files changed, 208 insertions(+), 48 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 6d883595a..8997c8b78 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
@@ -3408,7 +3408,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
         return transactions;
     }
 
-    public List<LoanTransaction> retrieveListOfTransactionsByType(LoanTransactionType transactionType) {
+    public List<LoanTransaction> retrieveListOfTransactionsByType(final LoanTransactionType transactionType) {
         final List<LoanTransaction> transactions = new ArrayList<>();
         for (final LoanTransaction transaction : this.loanTransactions) {
             if (transaction.isNotReversed() && transaction.getTypeOf().equals(transactionType)) {
@@ -4158,10 +4158,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
         return expectedDisbursementDate;
     }
 
-    /*
-     * Reason for derving
-     */
-
     public BigDecimal getDisburseAmountForTemplate() {
         BigDecimal principal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
         Collection<LoanDisbursementDetails> details = fetchUndisbursedDetail();
@@ -5205,6 +5201,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
         return currentDisbursementDetails;
     }
 
+    public LoanDisbursementDetails getDisbursementDetails(final LocalDate transactionDate, final BigDecimal transactionAmount) {
+        for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
+            if (!disbursementDetail.isReversed() && disbursementDetail.getDisbursementDate().equals(transactionDate)
+                    && (disbursementDetail.principal().compareTo(transactionAmount) == 0)) {
+                return disbursementDetail;
+            }
+        }
+        return null;
+    }
+
     public ChangedTransactionDetail updateDisbursementDateAndAmountForTranche(final LoanDisbursementDetails disbursementDetails,
             final JsonCommand command, final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
         final Locale locale = command.extractLocale();
@@ -6301,81 +6307,81 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
             List<Long> existingReversedTransactionIds, Loan loan) {
 
         validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO_LAST);
+        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO_LAST, getDisbursementDate());
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>();
+        List<LoanTransaction> loanTransactions = retrieveListOfTransactionsByType(LoanTransactionType.DISBURSEMENT);
+        loanTransactions.sort(Comparator.comparing(LoanTransaction::getId));
+        final LoanTransaction lastDisbursalTransaction = loanTransactions.get(loanTransactions.size() - 1);
+        final LocalDate lastTransactionDate = lastDisbursalTransaction.getTransactionDate();
+
         existingTransactionIds.addAll(findExistingTransactionIds());
         existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
-        final Map<String, Object> actualChanges = new LinkedHashMap<>();
-        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO_LAST, getDisbursementDate());
-        LocalDate actualDisbursementDate = null;
-        LocalDate lastTransactionDate = getDisbursementDate();
-        List<LoanTransaction> loanTransactions = retrieveListOfTransactionsExcludeAccruals();
+
+        loanTransactions = retrieveListOfTransactionsExcludeAccruals();
         Collections.reverse(loanTransactions);
         for (final LoanTransaction previousTransaction : loanTransactions) {
             if (lastTransactionDate.isBefore(previousTransaction.getTransactionDate())
                     && (previousTransaction.isRepaymentType() || previousTransaction.isWaiver() || previousTransaction.isChargePayment())) {
                 throw new UndoLastTrancheDisbursementException(previousTransaction.getId());
             }
-            if (previousTransaction.isDisbursement()) {
-                lastTransactionDate = previousTransaction.getTransactionDate();
+            if (previousTransaction.getId().compareTo(lastDisbursalTransaction.getId()) < 0) {
                 break;
             }
         }
-        actualDisbursementDate = lastTransactionDate;
-        updateLoanToLastDisbursalState(actualDisbursementDate);
+        final LoanDisbursementDetails disbursementDetail = loan.getDisbursementDetails(lastTransactionDate,
+                lastDisbursalTransaction.getAmount());
+        updateLoanToLastDisbursalState(disbursementDetail);
         for (Iterator<LoanTermVariations> iterator = this.loanTermVariations.iterator(); iterator.hasNext();) {
             LoanTermVariations loanTermVariations = iterator.next();
-            if ((loanTermVariations.getTermType().isDueDateVariation()
-                    && loanTermVariations.fetchDateValue().isAfter(actualDisbursementDate))
+            if ((loanTermVariations.getTermType().isDueDateVariation() && loanTermVariations.fetchDateValue().isAfter(lastTransactionDate))
                     || (loanTermVariations.getTermType().isEMIAmountVariation()
-                            && loanTermVariations.getTermApplicableFrom().compareTo(actualDisbursementDate) == 0 ? Boolean.TRUE
+                            && loanTermVariations.getTermApplicableFrom().compareTo(lastTransactionDate) == 0 ? Boolean.TRUE
                                     : Boolean.FALSE)
-                    || loanTermVariations.getTermApplicableFrom().isAfter(actualDisbursementDate)) {
+                    || loanTermVariations.getTermApplicableFrom().isAfter(lastTransactionDate)) {
                 iterator.remove();
             }
         }
-        reverseExistingTransactionsTillLastDisbursal(actualDisbursementDate);
+        reverseExistingTransactionsTillLastDisbursal(lastDisbursalTransaction);
         loan.recalculateScheduleFromLastTransaction(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds);
         actualChanges.put("undolastdisbursal", "true");
         actualChanges.put("disbursedAmount", this.getDisbursedAmount());
         updateLoanSummaryDerivedFields();
 
+        doPostLoanTransactionChecks(getLastUserTransactionDate(), loanLifecycleStateMachine);
+
         return actualChanges;
     }
 
     /**
      * Reverse only disbursement, accruals, and repayments at disbursal transactions
      *
-     * @param actualDisbursementDate
+     * @param lastDisbursalTransaction
      * @return
      */
-    public List<LoanTransaction> reverseExistingTransactionsTillLastDisbursal(LocalDate actualDisbursementDate) {
-        final List<LoanTransaction> reversedTransactions = new ArrayList<>();
+    public void reverseExistingTransactionsTillLastDisbursal(LoanTransaction lastDisbursalTransaction) {
         for (final LoanTransaction transaction : this.loanTransactions) {
-            if ((actualDisbursementDate.equals(transaction.getTransactionDate())
-                    || actualDisbursementDate.isBefore(transaction.getTransactionDate()))
+            if ((transaction.getTransactionDate().compareTo(lastDisbursalTransaction.getTransactionDate()) >= 0)
+                    && (transaction.getId().compareTo(lastDisbursalTransaction.getId()) >= 0)
                     && transaction.isAllowTypeTransactionAtTheTimeOfLastUndo()) {
-                reversedTransactions.add(transaction);
                 transaction.reverse();
             }
         }
-        return reversedTransactions;
     }
 
-    private void updateLoanToLastDisbursalState(LocalDate actualDisbursementDate) {
+    private void updateLoanToLastDisbursalState(LoanDisbursementDetails disbursementDetail) {
 
         for (final LoanCharge charge : getActiveCharges()) {
             if (charge.isOverdueInstallmentCharge()) {
                 charge.setActive(false);
-            } else if (charge.isTrancheDisbursementCharge() && actualDisbursementDate
+            } else if (charge.isTrancheDisbursementCharge() && disbursementDetail.getDisbursementDate()
                     .equals(charge.getTrancheDisbursementCharge().getloanDisbursementDetails().actualDisbursementDate())) {
                 charge.resetToOriginal(loanCurrency());
             }
         }
-        for (final LoanDisbursementDetails details : getDisbursementDetails()) {
-            if (actualDisbursementDate.equals(details.actualDisbursementDate())) {
-                this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount().subtract(details.principal()));
-                details.updateActualDisbursementDate(null);
-            }
-        }
+        this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount().subtract(disbursementDetail.principal()));
+        disbursementDetail.updateActualDisbursementDate(null);
+        disbursementDetail.reverse();
         updateLoanSummaryDerivedFields();
     }
 
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanApplicationUndoLastTrancheTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanApplicationUndoLastTrancheTest.java
index 65740ee72..7b1f0ca74 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanApplicationUndoLastTrancheTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanApplicationUndoLastTrancheTest.java
@@ -18,6 +18,8 @@
  */
 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;
@@ -25,9 +27,12 @@ import io.restassured.path.json.JsonPath;
 import io.restassured.specification.RequestSpecification;
 import io.restassured.specification.ResponseSpecification;
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
 import org.apache.fineract.integrationtests.common.Utils;
@@ -91,7 +96,7 @@ public class LoanApplicationUndoLastTrancheTest {
         approveTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("23 June 2014", "1000"));
 
         // APPLY FOR LOAN WITH TRANCHES
-        final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, createTranches);
+        final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, "2", createTranches);
         LOG.info("-----------------------------------LOAN CREATED WITH LOANID------------------------------------------------- {}", loanID);
         HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
 
@@ -129,6 +134,140 @@ public class LoanApplicationUndoLastTrancheTest {
         validateDisbursedAmount(disbursedAmount);
     }
 
+    @Test
+    public void loanApplicationUndoLastTrancheToClose() {
+        final LocalDate todaysDate = Utils.getLocalDateOfTenant();
+        LocalDate transactionDate = LocalDate.of(todaysDate.getYear(), 1, 1);
+        String operationDate = Utils.dateFormatter.format(transactionDate);
+        LOG.info("Operation date {}", transactionDate);
+
+        final String proposedAmount = "1000";
+
+        // CREATE CLIENT
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
+        LOG.info("---------------------------------CLIENT CREATED WITH ID--------------------------------------------------- {}", clientID);
+
+        // CREATE LOAN MULTIDISBURSAL PRODUCT
+        final Integer loanProductID = this.loanTransactionHelper
+                .getLoanProductId(new LoanProductTestBuilder().withInterestTypeAsDecliningBalance().withTranches(true)
+                        .withDisallowExpectedDisbursements(true).withInterestCalculationPeriodTypeAsRepaymentPeriod(true).build(null));
+        LOG.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}",
+                loanProductID);
+
+        // APPLY FOR LOAN WITH TRANCHES
+        final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, "0", new ArrayList<>());
+
+        LOG.info("-----------------------------------LOAN CREATED WITH LOANID------------------------------------------------- {}", loanID);
+
+        LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
+        this.loanTransactionHelper.approveLoan(operationDate, proposedAmount, loanID, null);
+
+        GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        assertNotNull(getLoansLoanIdResponse);
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.approved");
+
+        // DISBURSE A LOAN
+        loanTransactionHelper.disburseLoanWithTransactionAmount(operationDate, loanID, "500");
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        assertNotNull(getLoansLoanIdResponse);
+        // VALIDATE THE LOAN IS ACTIVE STATUS
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.active");
+        loanTransactionHelper.evaluateLoanDisbursementDetails(getLoansLoanIdResponse, 1, Double.valueOf("500.00"));
+
+        // DISBURSE A LOAN (second)
+        transactionDate = transactionDate.plusDays(2);
+        operationDate = Utils.dateFormatter.format(transactionDate);
+        LOG.info("Operation date {}", transactionDate);
+        loanTransactionHelper.disburseLoanWithTransactionAmount(operationDate, loanID, "500");
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        assertNotNull(getLoansLoanIdResponse);
+        // VALIDATE THE LOAN IS ACTIVE STATUS
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.active");
+        loanTransactionHelper.evaluateLoanDisbursementDetails(getLoansLoanIdResponse, 2, Double.valueOf("1000.00"));
+
+        // BACKDATE REPAYMENT
+        transactionDate = transactionDate.minusDays(1);
+        operationDate = Utils.dateFormatter.format(transactionDate);
+        LOG.info("Operation date {}", transactionDate);
+        Float amount = Float.valueOf("500.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);
+        // VALIDATE THE LOAN IS ACTIVE STATUS
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.active");
+        loanTransactionHelper.evaluateLoanDisbursementDetails(getLoansLoanIdResponse, 2, Double.valueOf("1000.00"));
+        loanTransactionHelper.validateLoanTotalOustandingBalance(getLoansLoanIdResponse, Double.valueOf("500.00"));
+
+        // UNDO LAST TRANCHE
+        this.loanTransactionHelper.undoLastDisbursal(loanID);
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        assertNotNull(getLoansLoanIdResponse);
+        // VALIDATE THE LOAN IS ACTIVE STATUS
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.closed.obligations.met");
+        loanTransactionHelper.validateLoanTotalOustandingBalance(getLoansLoanIdResponse, Double.valueOf("0.00"));
+    }
+
+    @Test
+    public void loanApplicationUndoLastTrancheWithSameDate() {
+
+        final String proposedAmount = "5000";
+        final String approveDate = "01 March 2014";
+        final String disbursalDate = "01 March 2014";
+
+        // CREATE CLIENT
+        final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
+        LOG.info("---------------------------------CLIENT CREATED WITH ID--------------------------------------------------- {}", clientID);
+
+        // CREATE LOAN MULTIDISBURSAL PRODUCT
+        final Integer loanProductID = this.loanTransactionHelper
+                .getLoanProductId(new LoanProductTestBuilder().withInterestTypeAsDecliningBalance().withTranches(true)
+                        .withDisallowExpectedDisbursements(true).withInterestCalculationPeriodTypeAsRepaymentPeriod(true).build(null));
+        LOG.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}",
+                loanProductID);
+
+        // APPLY FOR LOAN WITH TRANCHES
+        final Integer loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, "0", new ArrayList<>());
+
+        LOG.info("-----------------------------------LOAN CREATED WITH LOANID------------------------------------------------- {}", loanID);
+
+        LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
+        this.loanTransactionHelper.approveLoan(approveDate, proposedAmount, loanID, null);
+
+        GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        assertNotNull(getLoansLoanIdResponse);
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.approved");
+
+        // DISBURSE A LOAN
+        loanTransactionHelper.disburseLoanWithTransactionAmount(disbursalDate, loanID, "1000");
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        assertNotNull(getLoansLoanIdResponse);
+        // VALIDATE THE LOAN IS ACTIVE STATUS
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.active");
+        loanTransactionHelper.evaluateLoanDisbursementDetails(getLoansLoanIdResponse, 1, Double.valueOf("1000.00"));
+
+        // DISBURSE A LOAN (second)
+        loanTransactionHelper.disburseLoanWithTransactionAmount(disbursalDate, loanID, "2000");
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        assertNotNull(getLoansLoanIdResponse);
+        // VALIDATE THE LOAN IS ACTIVE STATUS
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.active");
+        loanTransactionHelper.evaluateLoanDisbursementDetails(getLoansLoanIdResponse, 2, Double.valueOf("3000.00"));
+
+        // UNDO LAST TRANCHE
+        this.loanTransactionHelper.undoLastDisbursal(loanID);
+
+        getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanID);
+        assertNotNull(getLoansLoanIdResponse);
+        // VALIDATE THE LOAN IS ACTIVE STATUS
+        loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.active");
+        loanTransactionHelper.evaluateLoanDisbursementDetails(getLoansLoanIdResponse, 1, Double.valueOf("1000.00"));
+        loanTransactionHelper.validateLoanTotalOustandingBalance(getLoansLoanIdResponse, Double.valueOf("1000.00"));
+    }
+
     private void validateDisbursedAmount(Float disbursedAmount) {
         Assertions.assertEquals(Float.valueOf("1000.0"), disbursedAmount);
 
@@ -146,7 +285,7 @@ public class LoanApplicationUndoLastTrancheTest {
     }
 
     public Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, String principal,
-            List<HashMap> tranches) {
+            final String interestRate, List<HashMap> tranches) {
         LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
         List<HashMap> collaterals = new ArrayList<>();
         final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec);
@@ -155,21 +294,23 @@ public class LoanApplicationUndoLastTrancheTest {
                 clientID.toString(), collateralId);
         Assertions.assertNotNull(clientCollateralId);
         addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
-        final String loanApplicationJSON = new LoanApplicationTestBuilder()
-                //
-                .withPrincipal(principal)
-                //
-                .withLoanTermFrequency("5")
-                //
-                .withLoanTermFrequencyAsMonths()
-                //
-                .withNumberOfRepayments("5").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() //
-                .withInterestRatePerPeriod("2") //
+        LoanApplicationTestBuilder loanApplication = new LoanApplicationTestBuilder() //
+                .withPrincipal(principal) //
+                .withLoanTermFrequency("5") //
+                .withLoanTermFrequencyAsMonths() //
+                .withNumberOfRepayments("5") //
+                .withRepaymentEveryAfter("1") //
+                .withRepaymentFrequencyTypeAsMonths() //
+                .withInterestRatePerPeriod(interestRate) //
                 .withExpectedDisbursementDate("01 March 2014") //
-                .withTranches(tranches) //
                 .withInterestTypeAsDecliningBalance() //
                 .withSubmittedOnDate("01 March 2014") //
-                .withCollaterals(collaterals).build(clientID.toString(), loanProductID.toString(), null);
+                .withCollaterals(collaterals);
+
+        if (tranches != null && tranches.size() > 0) {
+            loanApplication = loanApplication.withTranches(tranches);
+        }
+        final String loanApplicationJSON = loanApplication.build(clientID.toString(), loanProductID.toString(), null);
 
         return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
     }
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 cb3ba3993..2b9045de5 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
@@ -50,6 +50,7 @@ import org.apache.fineract.client.models.GetLoansApprovalTemplateResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdChargesChargeIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdChargesTemplateResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdCollectionData;
+import org.apache.fineract.client.models.GetLoansLoanIdDisbursementDetails;
 import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
 import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
@@ -1617,6 +1618,18 @@ public class LoanTransactionHelper extends IntegrationTest {
         }
     }
 
+    public void evaluateLoanDisbursementDetails(GetLoansLoanIdResponse getLoansLoanIdResponse, Integer numItems, Double amountExpected) {
+        log.info("Disbursement details items: {}", getLoansLoanIdResponse.getDisbursementDetails().size());
+        assertEquals(numItems, getLoansLoanIdResponse.getDisbursementDetails().size());
+        Double amount = Double.valueOf("0.0");
+        for (GetLoansLoanIdDisbursementDetails disbursementDetails : getLoansLoanIdResponse.getDisbursementDetails()) {
+            amount = amount + disbursementDetails.getPrincipal();
+            log.info("Disbursement details with principal {} {}", disbursementDetails.getExpectedDisbursementDate(),
+                    disbursementDetails.getPrincipal());
+        }
+        assertEquals(amountExpected, amount);
+    }
+
     public Long applyChargebackTransaction(final Integer loanId, final Long transactionId, final String amount,
             final Integer paymentTypeIdx, ResponseSpecification responseSpec) {
         List<GetPaymentTypesResponse> paymentTypeList = PaymentTypeHelper.getSystemPaymentType(this.requestSpec, this.responseSpec);