Allow multi-disbursements without setting up expected tranche details (#2129)

This commit is contained in:
John-Woodlock
2022-03-10 10:37:35 +01:00
committed by GitHub
parent 91a8c948d1
commit a25d07df20
6 changed files with 330 additions and 4 deletions
@@ -109,7 +109,6 @@ public interface LoanProductConstants {
BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_SINGLE_DISBURSE_LOAN = BigDecimal.valueOf(0); BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_SINGLE_DISBURSE_LOAN = BigDecimal.valueOf(0);
// Fixed installment configuration related // Fixed installment configuration related
String canDefineEmiAmountParamName = "canDefineInstallmentAmount"; String canDefineEmiAmountParamName = "canDefineInstallmentAmount";
String installmentAmountInMultiplesOfParamName = "installmentAmountInMultiplesOf";
String fixedPrincipalPercentagePerInstallmentParamName = "fixedPrincipalPercentagePerInstallment"; String fixedPrincipalPercentagePerInstallmentParamName = "fixedPrincipalPercentagePerInstallment";
// Loan Configurable Attributes // Loan Configurable Attributes
@@ -136,4 +135,11 @@ public interface LoanProductConstants {
String RATES_PARAM_NAME = "rates"; String RATES_PARAM_NAME = "rates";
// Multiple disbursement related
String installmentAmountInMultiplesOfParamName = "installmentAmountInMultiplesOf";
String DISALLOW_EXPECTED_DISBURSEMENTS = "disallowExpectedDisbursements";
String ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED = "allowApprovedDisbursedAmountsOverApplied";
String OVER_APPLIED_CALCULATION_TYPE = "overAppliedCalculationType";
String OVER_APPLIED_NUMBER = "overAppliedNumber";
} }
@@ -24,6 +24,7 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@@ -64,6 +65,7 @@ import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException;
import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.portfolio.rate.domain.Rate;
/** /**
@@ -361,6 +363,20 @@ public class LoanProduct extends AbstractPersistableCustom {
BigDecimal fixedPrincipalPercentagePerInstallment = command BigDecimal fixedPrincipalPercentagePerInstallment = command
.bigDecimalValueOfParameterNamed(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName); .bigDecimalValueOfParameterNamed(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName);
final boolean disallowExpectedDisbursements = command.parameterExists(LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS)
? command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS)
: false;
final boolean allowApprovedDisbursedAmountsOverApplied = command
.parameterExists(LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED)
? command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED)
: false;
final String overAppliedCalculationType = command
.stringValueOfParameterNamedAllowingNull(LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE);
final Integer overAppliedNumber = command.integerValueOfParameterNamed(LoanProductConstants.OVER_APPLIED_NUMBER);
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery, annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery,
@@ -375,7 +391,8 @@ public class LoanProduct extends AbstractPersistableCustom {
floatingRate, interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate, floatingRate, interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate,
defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed,
minimumGapBetweenInstallments, maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup, minimumGapBetweenInstallments, maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment); isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment, disallowExpectedDisbursements,
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber);
} }
@@ -610,7 +627,9 @@ public class LoanProduct extends AbstractPersistableCustom {
Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed, Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed,
final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments, final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments,
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization, final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization,
final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment) { final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean disallowExpectedDisbursements,
final boolean allowApprovedDisbursedAmountsOverApplied, final String overAppliedCalculationType,
final Integer overAppliedNumber) {
this.fund = fund; this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy; this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim(); this.name = name.trim();
@@ -689,9 +708,68 @@ public class LoanProduct extends AbstractPersistableCustom {
this.canUseForTopup = canUseForTopup; this.canUseForTopup = canUseForTopup;
this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment; this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment;
this.disallowExpectedDisbursements = disallowExpectedDisbursements;
this.allowApprovedDisbursedAmountsOverApplied = allowApprovedDisbursedAmountsOverApplied;
this.overAppliedCalculationType = overAppliedCalculationType;
this.overAppliedNumber = overAppliedNumber;
if (rates != null) { if (rates != null) {
this.rates = rates; this.rates = rates;
} }
validateLoanProductPreSave();
}
public void validateLoanProductPreSave() {
if (this.disallowExpectedDisbursements) {
if (!this.isMultiDisburseLoan()) {
throw new LoanProductGeneralRuleException("allowMultipleDisbursals.not.set.disallowExpectedDisbursements.cant.be.set",
"Allow Multiple Disbursals Not Set - Disallow Expected Disbursals Can't Be Set");
}
}
if (this.allowApprovedDisbursedAmountsOverApplied) {
if (!this.disallowExpectedDisbursements) {
throw new LoanProductGeneralRuleException(
"disallowExpectedDisbursements.not.set.allowApprovedDisbursedAmountsOverApplied.cant.be.set",
"Disallow Expected Disbursals Not Set - Allow Approved / Disbursed Amounts Over Applied Can't Be Set");
}
}
if (this.overAppliedCalculationType == null || this.overAppliedCalculationType.isEmpty()) {
if (this.allowApprovedDisbursedAmountsOverApplied) {
throw new LoanProductGeneralRuleException(
"allowApprovedDisbursedAmountsOverApplied.is.set.overAppliedCalculationType.is.mandatory",
"Allow Approved / Disbursed Amounts Over Applied is Set - Over Applied Calculation Type is Mandatory");
}
} else {
if (!this.allowApprovedDisbursedAmountsOverApplied) {
throw new LoanProductGeneralRuleException(
"allowApprovedDisbursedAmountsOverApplied.is.not.set.overAppliedCalculationType.cant.be.entered",
"Allow Approved / Disbursed Amounts Over Applied is Not Set - Over Applied Calculation Type Can't Be Entered");
}
List<String> overAppliedCalculationTypeAllowedValues = Arrays.asList("percentage", "flat");
if (!overAppliedCalculationTypeAllowedValues.contains(this.overAppliedCalculationType)) {
throw new LoanProductGeneralRuleException("overAppliedCalculationType.must.be.percentage.or.flat",
"Over Applied Calculation Type Must Be 'percentage' or 'flat'");
}
}
if (this.overAppliedNumber != null) {
if (!this.allowApprovedDisbursedAmountsOverApplied) {
throw new LoanProductGeneralRuleException(
"allowApprovedDisbursedAmountsOverApplied.is.not.set.overAppliedNumber.cant.be.entered",
"Allow Approved / Disbursed Amounts Over Applied is Not Set - Over Applied Number Can't Be Entered");
}
} else {
if (this.allowApprovedDisbursedAmountsOverApplied) {
throw new LoanProductGeneralRuleException("allowApprovedDisbursedAmountsOverApplied.is.set.overAppliedNumber.is.mandatory",
"Allow Approved / Disbursed Amounts Over Applied is Set - Over Applied Number is Mandatory");
}
}
} }
public MonetaryCurrency getCurrency() { public MonetaryCurrency getCurrency() {
@@ -1116,6 +1194,34 @@ public class LoanProduct extends AbstractPersistableCustom {
this.fixedPrincipalPercentagePerInstallment = newValue; this.fixedPrincipalPercentagePerInstallment = newValue;
} }
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS,
this.disallowExpectedDisbursements)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS);
actualChanges.put(LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS, newValue);
this.disallowExpectedDisbursements = newValue;
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED,
this.allowApprovedDisbursedAmountsOverApplied)) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED);
actualChanges.put(LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED, newValue);
this.allowApprovedDisbursedAmountsOverApplied = newValue;
}
if (command.isChangeInStringParameterNamed(LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE, this.overAppliedCalculationType)) {
final String newValue = command.stringValueOfParameterNamed(LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE);
actualChanges.put(LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE, newValue);
this.overAppliedCalculationType = newValue;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.OVER_APPLIED_NUMBER, this.overAppliedNumber)) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.OVER_APPLIED_NUMBER);
actualChanges.put(LoanProductConstants.OVER_APPLIED_NUMBER, newValue);
actualChanges.put("locale", localeAsInput);
this.overAppliedNumber = newValue;
}
return actualChanges; return actualChanges;
} }
@@ -1481,4 +1587,25 @@ public class LoanProduct extends AbstractPersistableCustom {
public Integer getOverAppliedNumber() { public Integer getOverAppliedNumber() {
return overAppliedNumber; return overAppliedNumber;
} }
public void setDisallowExpectedDisbursements(boolean disallowExpectedDisbursements) {
this.disallowExpectedDisbursements = disallowExpectedDisbursements;
}
public void setAllowApprovedDisbursedAmountsOverApplied(boolean allowApprovedDisbursedAmountsOverApplied) {
this.allowApprovedDisbursedAmountsOverApplied = allowApprovedDisbursedAmountsOverApplied;
}
public void setOverAppliedCalculationType(String overAppliedCalculationType) {
this.overAppliedCalculationType = overAppliedCalculationType;
}
public void setOverAppliedNumber(Integer overAppliedNumber) {
this.overAppliedNumber = overAppliedNumber;
}
public void setLoanProducTrancheDetails(LoanProductTrancheDetails loanProducTrancheDetails) {
this.loanProducTrancheDetails = loanProducTrancheDetails;
}
} }
@@ -0,0 +1,31 @@
/**
* 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.loanproduct.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when lending strategy mismatch occurs
*/
public class LoanProductGeneralRuleException extends AbstractPlatformDomainRuleException {
public LoanProductGeneralRuleException(String errorMsg, String errorMsgEnglish) {
super("error.msg." + errorMsg, errorMsgEnglish);
}
}
@@ -109,7 +109,9 @@ public final class LoanProductDataValidator {
LoanProductConstants.recalculationRestFrequencyOnDayParamName, LoanProductConstants.recalculationRestFrequencyOnDayParamName,
LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName, LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName,
LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME, LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME,
LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName)); LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS,
LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED, LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE,
LoanProductConstants.OVER_APPLIED_NUMBER));
private static final String[] supportedloanConfigurableAttributes = { LoanProductConstants.amortizationTypeParamName, private static final String[] supportedloanConfigurableAttributes = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyIdParamName, LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyIdParamName,
@@ -753,6 +755,7 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter("interestType").value(interestType).ignoreIfNull() baseDataValidator.reset().parameter("interestType").value(interestType).ignoreIfNull()
.integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue()); .integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
} }
} }
private void validateInterestRecalculationParams(final JsonElement element, final DataValidatorBuilder baseDataValidator, private void validateInterestRecalculationParams(final JsonElement element, final DataValidatorBuilder baseDataValidator,
@@ -252,6 +252,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
product.validateLoanProductPreSave();
this.loanProductRepository.saveAndFlush(product); this.loanProductRepository.saveAndFlush(product);
} }
@@ -0,0 +1,158 @@
/**
* 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.loanproduct.domain;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThrows;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class LoanProductTest {
@Test
public void shouldThrowExceptionWhenAllowMultipleDisbursalsFalseAndDisallowExpectedDisbursementsIsTrue() {
LoanProduct lp = stubLoanProduct(false);
lp.setDisallowExpectedDisbursements(true);
LoanProductGeneralRuleException expectedEx = assertThrows(LoanProductGeneralRuleException.class,
() -> lp.validateLoanProductPreSave());
assertThat(expectedEx.getGlobalisationMessageCode(),
is("error.msg.allowMultipleDisbursals.not.set.disallowExpectedDisbursements.cant.be.set"));
}
@Test
public void shouldThrowExceptionWhenDisallowExpectedDisbursementIsFalseAndAllowApprovedDisbursedAmountsOverAppliedIsTrue() {
LoanProduct lp = stubLoanProduct(true);
lp.setDisallowExpectedDisbursements(false);
lp.setAllowApprovedDisbursedAmountsOverApplied(true);
LoanProductGeneralRuleException expectedEx = assertThrows(LoanProductGeneralRuleException.class,
() -> lp.validateLoanProductPreSave());
assertThat(expectedEx.getGlobalisationMessageCode(),
is("error.msg.disallowExpectedDisbursements.not.set.allowApprovedDisbursedAmountsOverApplied.cant.be.set"));
}
@Test
public void shouldThrowExceptionWhenAllowApprovedDisbursedAmountsOverAppliedIsTrueAndAppliedCalculationTypeIsNull() {
LoanProduct lp = stubLoanProduct(true);
lp.setDisallowExpectedDisbursements(true);
lp.setAllowApprovedDisbursedAmountsOverApplied(true);
lp.setOverAppliedCalculationType(null);
LoanProductGeneralRuleException expectedEx = assertThrows(LoanProductGeneralRuleException.class,
() -> lp.validateLoanProductPreSave());
assertThat(expectedEx.getGlobalisationMessageCode(),
is("error.msg.allowApprovedDisbursedAmountsOverApplied.is.set.overAppliedCalculationType.is.mandatory"));
}
@Test
public void shouldThrowExceptionWhenAllowApprovedDisbursedAmountsOverAppliedIsTrueAndAppliedCalculationTypeIsEmpty() {
LoanProduct lp = stubLoanProduct(true);
lp.setDisallowExpectedDisbursements(true);
lp.setAllowApprovedDisbursedAmountsOverApplied(true);
lp.setOverAppliedCalculationType("");
LoanProductGeneralRuleException expectedEx = assertThrows(LoanProductGeneralRuleException.class,
() -> lp.validateLoanProductPreSave());
assertThat(expectedEx.getGlobalisationMessageCode(),
is("error.msg.allowApprovedDisbursedAmountsOverApplied.is.set.overAppliedCalculationType.is.mandatory"));
}
@Test
public void shouldThrowExceptionWhenAllowApprovedDisbursedAmountsOverAppliedIsFalseAndAppliedCalculationTypeIsNotEmpty() {
LoanProduct lp = stubLoanProduct(true);
lp.setDisallowExpectedDisbursements(true);
lp.setAllowApprovedDisbursedAmountsOverApplied(false);
lp.setOverAppliedCalculationType("flat");
LoanProductGeneralRuleException expectedEx = assertThrows(LoanProductGeneralRuleException.class,
() -> lp.validateLoanProductPreSave());
assertThat(expectedEx.getGlobalisationMessageCode(),
is("error.msg.allowApprovedDisbursedAmountsOverApplied.is.not.set.overAppliedCalculationType.cant.be.entered"));
}
@Test
public void shouldThrowExceptionWhenOverAppliedCalculationTypeNotValid() {
LoanProduct lp = stubLoanProduct(true);
lp.setDisallowExpectedDisbursements(true);
lp.setAllowApprovedDisbursedAmountsOverApplied(true);
lp.setOverAppliedCalculationType("notflat");
LoanProductGeneralRuleException expectedEx = assertThrows(LoanProductGeneralRuleException.class,
() -> lp.validateLoanProductPreSave());
assertThat(expectedEx.getGlobalisationMessageCode(), is("error.msg.overAppliedCalculationType.must.be.percentage.or.flat"));
}
@Test
public void shouldThrowExceptionWhenAllowApprovedDisbursedAmountsOverAppliedIsTrueAndAppliedCalculationNumberIsNull() {
LoanProduct lp = stubLoanProduct(true);
lp.setDisallowExpectedDisbursements(true);
lp.setAllowApprovedDisbursedAmountsOverApplied(true);
lp.setOverAppliedCalculationType("flat");
lp.setOverAppliedNumber(null);
LoanProductGeneralRuleException expectedEx = assertThrows(LoanProductGeneralRuleException.class,
() -> lp.validateLoanProductPreSave());
assertThat(expectedEx.getGlobalisationMessageCode(),
is("error.msg.allowApprovedDisbursedAmountsOverApplied.is.set.overAppliedNumber.is.mandatory"));
}
@Test
public void shouldThrowExceptionWhenAllowApprovedDisbursedAmountsOverAppliedIsFalseAndAppliedCalculationNumberIsNotNull() {
LoanProduct lp = stubLoanProduct(true);
lp.setDisallowExpectedDisbursements(true);
lp.setAllowApprovedDisbursedAmountsOverApplied(false);
lp.setOverAppliedNumber(80);
LoanProductGeneralRuleException expectedEx = assertThrows(LoanProductGeneralRuleException.class,
() -> lp.validateLoanProductPreSave());
assertThat(expectedEx.getGlobalisationMessageCode(),
is("error.msg.allowApprovedDisbursedAmountsOverApplied.is.not.set.overAppliedNumber.cant.be.entered"));
}
private LoanProduct stubLoanProduct(boolean allowMultipleDisbursals) {
LoanProduct lp = new LoanProduct();
lp.setLoanProducTrancheDetails(new LoanProductTrancheDetails(allowMultipleDisbursals, null, null));
return lp;
}
}