You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by al...@apache.org on 2022/03/03 18:15:45 UTC

[fineract] branch develop updated: FINERACT-1515 Allow multi-disbursements without setting up expected tranche details

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

aleks 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 de64de8  FINERACT-1515 Allow multi-disbursements without setting up expected tranche details
de64de8 is described below

commit de64de876cf5a13920b3f6d4ca3ff07653933750
Author: John Woodlock <jo...@gmail.com>
AuthorDate: Thu Mar 3 12:03:14 2022 +0000

    FINERACT-1515 Allow multi-disbursements without setting up expected tranche details
---
 .../portfolio/loanaccount/domain/Loan.java         | 124 +++++++++++++++------
 .../MultiDisbursementDataNotAllowedException.java  |  30 +++++
 .../loanaccount/service/LoanAssembler.java         |  17 ++-
 .../LoanWritePlatformServiceJpaRepositoryImpl.java |  44 ++++++--
 .../portfolio/loanproduct/domain/LoanProduct.java  |  28 +++++
 .../db/changelog/tenant/changelog-tenant.xml       |   2 +
 ...oduct_loan_disallow_expected_disbursements.xml} |  10 +-
 ...higher_than_applied_loan_amount_management.xml} |  12 +-
 8 files changed, 213 insertions(+), 54 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 864536a..2ccb667 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
@@ -121,6 +121,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureExcept
 import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
 import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
 import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
+import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataNotAllowedException;
 import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
 import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
@@ -1330,8 +1331,10 @@ public class Loan extends AbstractPersistableCustom {
     }
 
     public void updateLoanScheduleDependentDerivedFields() {
-        this.expectedMaturityDate = Date.from(determineExpectedMaturityDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
-        this.actualMaturityDate = Date.from(determineExpectedMaturityDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
+        if (this.getLoanRepaymentScheduleInstallmentsSize() > 0) {
+            this.expectedMaturityDate = Date.from(determineExpectedMaturityDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
+            this.actualMaturityDate = Date.from(determineExpectedMaturityDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
+        }
     }
 
     private void updateLoanSummaryDerivedFields() {
@@ -1620,9 +1623,16 @@ public class Loan extends AbstractPersistableCustom {
             }
             final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
 
-            if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
-                final String errorMessage = "For this loan product, disbursement details must be provided";
-                throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+            if (loanProduct.isDisallowExpectedDisbursements()) {
+                if (disbursementDataArray != null) {
+                    final String errorMessage = "For this loan product, disbursement details are not allowed";
+                    throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+                }
+            } else {
+                if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
+                    final String errorMessage = "For this loan product, disbursement details must be provided";
+                    throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+                }
             }
             if (disbursementDataArray.size() > loanProduct.maxTrancheCount()) {
                 final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
@@ -2224,31 +2234,19 @@ public class Loan extends AbstractPersistableCustom {
             LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate");
 
             BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName);
-
             if (approvedLoanAmount != null) {
+                compareApprovedToProposedPrincipal(approvedLoanAmount);
 
-                // Approved amount has to be less than or equal to principal
-                // amount demanded
-
-                if (approvedLoanAmount.compareTo(this.proposedPrincipal) < 0) {
-
-                    this.approvedPrincipal = approvedLoanAmount;
+                /*
+                 * All the calculations are done based on the principal amount, so it is necessary to set principal
+                 * amount to approved amount
+                 */
+                this.approvedPrincipal = approvedLoanAmount;
 
-                    /*
-                     * All the calculations are done based on the principal amount, so it is necessary to set principal
-                     * amount to approved amount
-                     */
-
-                    this.loanRepaymentScheduleDetail.setPrincipal(approvedLoanAmount);
-
-                    actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount);
-                    actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount);
-                    actualChanges.put(LoanApiConstants.disbursementNetDisbursalAmountParameterName, netDisbursalAmount);
-                } else if (approvedLoanAmount.compareTo(this.proposedPrincipal) > 0) {
-                    final String errorMessage = "Loan approved amount can't be greater than loan amount demanded.";
-                    throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded",
-                            errorMessage, this.proposedPrincipal, approvedLoanAmount);
-                }
+                this.loanRepaymentScheduleDetail.setPrincipal(approvedLoanAmount);
+                actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount);
+                actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount);
+                actualChanges.put(LoanApiConstants.disbursementNetDisbursalAmountParameterName, netDisbursalAmount);
 
                 /* Update disbursement details */
                 if (disbursementDataArray != null) {
@@ -2259,10 +2257,16 @@ public class Loan extends AbstractPersistableCustom {
             recalculateAllCharges();
 
             if (loanProduct.isMultiDisburseLoan()) {
-
-                if (this.disbursementDetails.isEmpty()) {
-                    final String errorMessage = "For this loan product, disbursement details must be provided";
-                    throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+                if (loanProduct.isDisallowExpectedDisbursements()) {
+                    if (!disbursementDetails.isEmpty()) {
+                        final String errorMessage = "For this loan product, disbursement details are not allowed";
+                        throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+                    }
+                } else {
+                    if (disbursementDetails.isEmpty()) {
+                        final String errorMessage = "For this loan product, disbursement details must be provided";
+                        throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+                    }
                 }
 
                 if (this.disbursementDetails.size() > loanProduct.maxTrancheCount()) {
@@ -2313,6 +2317,39 @@ public class Loan extends AbstractPersistableCustom {
         }
 
         return actualChanges;
+
+    }
+
+    private void compareApprovedToProposedPrincipal(BigDecimal approvedLoanAmount) {
+
+        if (this.loanProduct().isDisallowExpectedDisbursements() && this.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
+            BigDecimal maxApprovedLoanAmount = getOverAppliedMax();
+            if (approvedLoanAmount.compareTo(maxApprovedLoanAmount) > 0) {
+                final String errorMessage = "Loan approved amount can't be greater than maximum applied loan amount calculation.";
+                throw new InvalidLoanStateTransitionException("approval",
+                        "amount.can't.be.greater.than.maximum.applied.loan.amount.calculation", errorMessage, approvedLoanAmount,
+                        maxApprovedLoanAmount);
+            }
+        } else {
+            if (approvedLoanAmount.compareTo(this.proposedPrincipal) > 0) {
+                final String errorMessage = "Loan approved amount can't be greater than loan amount demanded.";
+                throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded", errorMessage,
+                        this.proposedPrincipal, approvedLoanAmount);
+            }
+        }
+    }
+
+    private BigDecimal getOverAppliedMax() {
+        BigDecimal maxAmount = null;
+        if (this.getLoanProduct().getOverAppliedCalculationType().equals("percentage")) {
+            BigDecimal overAppliedNumber = BigDecimal.valueOf(getLoanProduct().getOverAppliedNumber());
+            BigDecimal x = overAppliedNumber.divide(BigDecimal.valueOf(100));
+            BigDecimal totalPercentage = BigDecimal.valueOf(1).add(x);
+            maxAmount = this.proposedPrincipal.multiply(totalPercentage);
+        } else {
+            maxAmount = this.proposedPrincipal.add(BigDecimal.valueOf(getLoanProduct().getOverAppliedNumber()));
+        }
+        return maxAmount;
     }
 
     public Map<String, Object> undoApproval(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
@@ -2542,11 +2579,7 @@ public class Loan extends AbstractPersistableCustom {
                     totalAmount = totalAmount.add(disbursementDetails.principal());
                 }
                 this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
-                if (totalAmount.compareTo(this.approvedPrincipal) > 0) {
-                    final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal ";
-                    throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", principalDisbursed,
-                            this.approvedPrincipal);
-                }
+                compareDisbursedToApprovedOrProposedPrincipal(disburseAmount.getAmount(), totalAmount);
             } else {
                 this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
             }
@@ -2559,6 +2592,25 @@ public class Loan extends AbstractPersistableCustom {
         return disburseAmount;
     }
 
+    private void compareDisbursedToApprovedOrProposedPrincipal(BigDecimal disbursedAmount, BigDecimal totalDisbursed) {
+
+        if (this.loanProduct().isDisallowExpectedDisbursements() && this.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
+            BigDecimal maxDisbursedAmount = getOverAppliedMax();
+            if (disbursedAmount.compareTo(maxDisbursedAmount) > 0) {
+                final String errorMessage = "Loan disbursal amount can't be greater than maximum applied loan amount calculation.";
+                throw new InvalidLoanStateTransitionException("disbursal",
+                        "amount.can't.be.greater.than.maximum.applied.loan.amount.calculation", errorMessage, disbursedAmount,
+                        maxDisbursedAmount);
+            }
+        } else {
+            if (totalDisbursed.compareTo(this.approvedPrincipal) > 0) {
+                final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal ";
+                throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed,
+                        this.approvedPrincipal);
+            }
+        }
+    }
+
     private ChangedTransactionDetail reprocessTransactionForDisbursement() {
         ChangedTransactionDetail changedTransactionDetail = null;
         if (this.loanProduct.isMultiDisburseLoan()) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/MultiDisbursementDataNotAllowedException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/MultiDisbursementDataNotAllowedException.java
new file mode 100644
index 0000000..9a9cadd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/MultiDisbursementDataNotAllowedException.java
@@ -0,0 +1,30 @@
+/**
+ * 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.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class MultiDisbursementDataNotAllowedException extends AbstractPlatformDomainRuleException {
+
+    public MultiDisbursementDataNotAllowedException(final String entity, final String defaultUserMessage,
+            final Object... defaultUserMessageArgs) {
+        super("error.msg." + entity + ".not.allowed", defaultUserMessage, defaultUserMessageArgs);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
index ffaa632..60f31d1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
@@ -75,6 +75,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionProcessin
 import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
 import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals;
 import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataNotAllowedException;
 import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
@@ -200,13 +201,21 @@ public class LoanAssembler {
         }
         BigDecimal maxOutstandingLoanBalance = null;
         if (loanProduct.isMultiDisburseLoan()) {
-            disbursementDetails = this.loanUtilService.fetchDisbursementData(element.getAsJsonObject());
             final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
             maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
                     element, locale);
-            if (disbursementDetails.isEmpty()) {
-                final String errorMessage = "For this loan product, disbursement details must be provided";
-                throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+
+            disbursementDetails = this.loanUtilService.fetchDisbursementData(element.getAsJsonObject());
+            if (loanProduct.isDisallowExpectedDisbursements()) {
+                if (!disbursementDetails.isEmpty()) {
+                    final String errorMessage = "For this loan product, disbursement details are not allowed";
+                    throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+                }
+            } else {
+                if (disbursementDetails.isEmpty()) {
+                    final String errorMessage = "For this loan product, disbursement details must be provided";
+                    throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+                }
             }
 
             if (disbursementDetails.size() > loanProduct.maxTrancheCount()) {
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 7389c9d..30940fc 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
@@ -173,6 +173,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursement
 import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
 import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
 import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
+import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataNotAllowedException;
 import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
 import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
@@ -373,6 +374,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
         }
 
         final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        if (loan.loanProduct().isDisallowExpectedDisbursements()) {
+            // create artificial 'tranche/expected disbursal' as current disburse code expects it for multi-disbursal
+            // products
+            final Date artificialExpectedDate = Date
+                    .from(loan.getExpectedDisbursedOnLocalDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
+            LoanDisbursementDetails disbursementDetail = new LoanDisbursementDetails(artificialExpectedDate, null,
+                    loan.getDisbursedAmount(), null);
+            disbursementDetail.updateLoan(loan);
+            loan.getDisbursementDetails().add(disbursementDetail);
+        }
 
         // Get disbursedAmount
         final BigDecimal disbursedAmount = loan.getDisbursedAmount();
@@ -2900,17 +2911,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
                 .build();
     }
 
-    private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate) {
+    private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate,
+            boolean isDisallowExpectedDisbursements) {
         final String json = command.json();
         final JsonElement element = this.fromApiJsonHelper.parse(json);
 
         final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
         final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
         final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
-        if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
-            final String errorMessage = "For this loan product, disbursement details must be provided";
-            throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+
+        if (isDisallowExpectedDisbursements) {
+            if (disbursementDataArray != null) {
+                final String errorMessage = "For this loan product, disbursement details are not allowed";
+                throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+            }
+        } else {
+            if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
+                final String errorMessage = "For this loan product, disbursement details must be provided";
+                throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+            }
         }
+
         final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element);
 
         loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate,
@@ -2952,15 +2973,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
             final String errorMessage = "cannot.modify.tranches.if.loan.is.pendingapproval.closed.overpaid.writtenoff";
             throw new LoanMultiDisbursementException(errorMessage);
         }
-        validateMultiDisbursementData(command, expectedDisbursementDate);
+        validateMultiDisbursementData(command, expectedDisbursementDate, loan.loanProduct().isDisallowExpectedDisbursements());
 
         this.validateForAddAndDeleteTranche(loan);
 
         loan.updateDisbursementDetails(command, actualChanges);
 
-        if (loan.getDisbursementDetails().isEmpty()) {
-            final String errorMessage = "For this loan product, disbursement details must be provided";
-            throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+        if (loan.loanProduct().isDisallowExpectedDisbursements()) {
+            if (!loan.getDisbursementDetails().isEmpty()) {
+                final String errorMessage = "For this loan product, disbursement details are not allowed";
+                throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+            }
+        } else {
+            if (loan.getDisbursementDetails().isEmpty()) {
+                final String errorMessage = "For this loan product, disbursement details must be provided";
+                throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
+            }
         }
 
         if (loan.getDisbursementDetails().size() > loan.loanProduct().maxTrancheCount()) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
index 3df21b7..c3c2fc5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
@@ -191,6 +191,18 @@ public class LoanProduct extends AbstractPersistableCustom {
     @Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5, nullable = true)
     private BigDecimal fixedPrincipalPercentagePerInstallment;
 
+    @Column(name = "disallow_expected_disbursements", nullable = false)
+    private boolean disallowExpectedDisbursements;
+
+    @Column(name = "allow_approved_disbursed_amounts_over_applied", nullable = false)
+    private boolean allowApprovedDisbursedAmountsOverApplied;
+
+    @Column(name = "over_applied_calculation_type", nullable = true)
+    private String overAppliedCalculationType;
+
+    @Column(name = "over_applied_number", nullable = true)
+    private Integer overAppliedNumber;
+
     public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
             final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate,
             final List<Rate> productRates) {
@@ -1461,4 +1473,20 @@ public class LoanProduct extends AbstractPersistableCustom {
     public BigDecimal getFixedPrincipalPercentagePerInstallment() {
         return fixedPrincipalPercentagePerInstallment;
     }
+
+    public boolean isDisallowExpectedDisbursements() {
+        return disallowExpectedDisbursements;
+    }
+
+    public boolean isAllowApprovedDisbursedAmountsOverApplied() {
+        return allowApprovedDisbursedAmountsOverApplied;
+    }
+
+    public String getOverAppliedCalculationType() {
+        return overAppliedCalculationType;
+    }
+
+    public Integer getOverAppliedNumber() {
+        return overAppliedNumber;
+    }
 }
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 1afbbe2..d340eb2 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -25,4 +25,6 @@
     <include file="parts/0003_postgresql_specific_initial_data.xml" relativeToChangelogFile="true"/>
     <include file="parts/0004_camelcase_column_renaming.xml" relativeToChangelogFile="true"/>
     <include file="parts/0005_savings_transaction_reversal.xml" relativeToChangelogFile="true"/>
+    <include file="parts/0006_product_loan_disallow_expected_disbursements.xml" relativeToChangelogFile="true"/>
+    <include file="parts/0007_product_loan_higher_than_applied_loan_amount_management.xml" relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0006_product_loan_disallow_expected_disbursements.xml
similarity index 79%
copy from fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
copy to fineract-provider/src/main/resources/db/changelog/tenant/parts/0006_product_loan_disallow_expected_disbursements.xml
index 1afbbe2..8b044bc 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0006_product_loan_disallow_expected_disbursements.xml
@@ -22,7 +22,11 @@
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
-    <include file="parts/0003_postgresql_specific_initial_data.xml" relativeToChangelogFile="true"/>
-    <include file="parts/0004_camelcase_column_renaming.xml" relativeToChangelogFile="true"/>
-    <include file="parts/0005_savings_transaction_reversal.xml" relativeToChangelogFile="true"/>
+    <changeSet author="fineract" id="1">
+        <addColumn tableName="m_product_loan">
+            <column name="disallow_expected_disbursements" type="BOOLEAN" defaultValueBoolean="false">
+                <constraints nullable="false"/>
+            </column>
+        </addColumn>
+    </changeSet>
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0007_product_loan_higher_than_applied_loan_amount_management.xml
similarity index 70%
copy from fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
copy to fineract-provider/src/main/resources/db/changelog/tenant/parts/0007_product_loan_higher_than_applied_loan_amount_management.xml
index 1afbbe2..fbb98be 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0007_product_loan_higher_than_applied_loan_amount_management.xml
@@ -22,7 +22,13 @@
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
-    <include file="parts/0003_postgresql_specific_initial_data.xml" relativeToChangelogFile="true"/>
-    <include file="parts/0004_camelcase_column_renaming.xml" relativeToChangelogFile="true"/>
-    <include file="parts/0005_savings_transaction_reversal.xml" relativeToChangelogFile="true"/>
+    <changeSet author="fineract" id="1">
+        <addColumn tableName="m_product_loan">
+            <column name="allow_approved_disbursed_amounts_over_applied" type="BOOLEAN" defaultValueBoolean="false">
+                <constraints nullable="false"/>
+            </column>
+            <column name="over_applied_calculation_type" type="VARCHAR(10)"/>
+            <column defaultValueComputed="NULL" name="over_applied_number" type="INT"/>
+        </addColumn>
+    </changeSet>
 </databaseChangeLog>