WIP: add min and max constraint to Principal amount

This commit is contained in:
ashok-conflux
2013-04-03 19:37:00 +05:30
parent 979308e41c
commit f077733cfb
14 changed files with 243 additions and 50 deletions
@@ -0,0 +1,7 @@
ALTER TABLE `m_product_loan`
ADD COLUMN `min_principal_amount` DECIMAL(19,6) NOT NULL AFTER `principal_amount`,
ADD COLUMN `max_principal_amount` DECIMAL(19,6) NOT NULL AFTER `min_principal_amount`;
ALTER TABLE `m_loan`
ADD COLUMN `min_principal_amount` DECIMAL(19,6) NOT NULL AFTER `principal_amount`,
ADD COLUMN `max_principal_amount` DECIMAL(19,6) NOT NULL AFTER `min_principal_amount`;
@@ -372,6 +372,23 @@ public class DataValidatorBuilder {
return this;
}
public DataValidatorBuilder inMinAndMaxAmountRange(final BigDecimal minimumAmount, final BigDecimal maximumAmount) {
if (minimumAmount != null && maximumAmount != null && value != null){
BigDecimal amount = BigDecimal.valueOf(Double.valueOf(value.toString()));
if (amount.compareTo(minimumAmount) == -1 || amount.compareTo(maximumAmount) == 1) {
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
.append(".amount.is.not.within.min.max.range");
StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter) .append(" amount ").append(amount)
.append(" must be between ").append(minimumAmount).append(" and ").append(maximumAmount).append(" .");
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(),
defaultEnglishMessage.toString(), parameter, amount, minimumAmount, maximumAmount);
dataValidationErrors.add(error);
return this;
}
}
return this;
}
public DataValidatorBuilder comapareMinAndMaxOfTwoBigDecmimalNos(final BigDecimal min, final BigDecimal max) {
if (min != null && max != null)
if (max.compareTo(min) == -1) {
@@ -1317,6 +1317,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanScheduleGenerator loanScheduleGenerator = new DefaultLoanScheduleGeneratorFactory().create(interestMethod);
final BigDecimal principal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
final BigDecimal minPrincipal = this.loanRepaymentScheduleDetail.getMinPrincipal().getAmount();
final BigDecimal maxPrincipal = this.loanRepaymentScheduleDetail.getMaxPrincipal().getAmount();
final BigDecimal inArrearsTolerance = this.loanRepaymentScheduleDetail.getInArrearsTolerance().getAmount();
final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod();
final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
@@ -1331,11 +1333,11 @@ public class Loan extends AbstractPersistable<Long> {
final Integer loanTermFrequency = this.termFrequency;
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
final LoanSchedule loanSchedule = new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod,
interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod,
repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency,
loanTermPeriodFrequencyType, setOfLoanCharges(), this.getDisbursementDate(), this.getExpectedFirstRepaymentOnDate(),
this.getInterestChargedFromDate(), inArrearsTolerance);
final LoanSchedule loanSchedule = new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod,
interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod,
loanTermFrequency, loanTermPeriodFrequencyType, setOfLoanCharges(), this.getDisbursementDate(),
this.getExpectedFirstRepaymentOnDate(), this.getInterestChargedFromDate(), inArrearsTolerance);
final LoanScheduleData generatedData = loanSchedule.generate();
@@ -27,6 +27,8 @@ public final class LoanSchedule {
private final LoanScheduleGenerator loanScheduleGenerator;
private final ApplicationCurrency applicationCurrency;
private final BigDecimal principal;
private final BigDecimal minPrincipal;
private final BigDecimal maxPrincipal;
private final BigDecimal nominalInterestRatePerPeriod;
private final PeriodFrequencyType interestRatePeriodFrequencyType;
private final BigDecimal nominalAnnualInterestRate;
@@ -46,17 +48,19 @@ public final class LoanSchedule {
private final Set<LoanCharge> loanCharges;
public LoanSchedule(final LoanScheduleGenerator loanScheduleGenerator, final ApplicationCurrency applicationCurrency,
final BigDecimal principal, final BigDecimal nominalInterestRatePerPeriod,
final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate,
final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod,
final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments,
final BigDecimal principal, final BigDecimal minPrincipal, final BigDecimal maxPrincipal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments,
final AmortizationMethod amortizationMethod, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Set<LoanCharge> loanCharges, final LocalDate disbursementDate,
final LocalDate repaymentStartFromDate, final LocalDate interestChargedFromDate,
final BigDecimal inArrearsTolerance) {
final LocalDate repaymentStartFromDate, final LocalDate interestChargedFromDate, final BigDecimal inArrearsTolerance) {
this.loanScheduleGenerator = loanScheduleGenerator;
this.applicationCurrency = applicationCurrency;
this.principal = principal;
this.minPrincipal = minPrincipal;
this.maxPrincipal = maxPrincipal;
this.nominalInterestRatePerPeriod = nominalInterestRatePerPeriod;
this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType;
this.nominalAnnualInterestRate = nominalAnnualInterestRate;
@@ -83,9 +87,9 @@ public final class LoanSchedule {
public LoanProductRelatedDetail loanProductRelatedDetail() {
final MonetaryCurrency currency = new MonetaryCurrency(applicationCurrency.getCode(), applicationCurrency.getDecimalPlaces());
return LoanProductRelatedDetail.createFrom(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType,
numberOfRepayments, amortizationMethod, this.inArrearsTolerance);
return LoanProductRelatedDetail.createFrom(currency, principal, minPrincipal, maxPrincipal, nominalInterestRatePerPeriod,
interestRatePeriodFrequencyType, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod,
repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, this.inArrearsTolerance);
}
public Integer getLoanTermFrequency() {
@@ -72,6 +72,8 @@ public class LoanScheduleAssembler {
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(currency.getCode());
final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount();//Copy minPrincipal from loan product to loan
final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount();//Copy maxPrincipal from loan product to loan
final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element);
final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestRateFrequencyType", element);
final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element);
@@ -105,7 +107,7 @@ public class LoanScheduleAssembler {
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod);
return new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod,
return new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod,
interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod,
repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency,
loanTermPeriodFrequencyType, loanCharges, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate, inArrearsTolerance);
@@ -22,6 +22,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -591,4 +592,21 @@ public final class LoanApplicationCommandFromApiJsonHelper {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct){
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount();
final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount();
final String principalParameterName = "principal";
if (fromApiJsonHelper.parameterExists(principalParameterName, element)) {
final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element);
baseDataValidator.reset().parameter(principalParameterName).value(principal).notNull().positiveAmount().inMinAndMaxAmountRange(minPrincipal, maxPrincipal);
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@@ -42,6 +42,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingS
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
@@ -68,6 +69,7 @@ public class LoanAssembler {
private final FromJsonHelper fromApiJsonHelper;
private final LoanSummaryWrapper loanSummaryWrapper;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
@Autowired
public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
@@ -76,7 +78,8 @@ public class LoanAssembler {
final StaffRepository staffRepository, final CodeValueRepositoryWrapper codeValueRepository,
final LoanScheduleAssembler loanScheduleAssembler, final LoanChargeAssembler loanChargeAssembler,
final CollateralAssembler loanCollateralAssembler, final LoanSummaryWrapper loanSummaryWrapper,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) {
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.loanProductRepository = loanProductRepository;
this.clientRepository = clientRepository;
@@ -90,6 +93,7 @@ public class LoanAssembler {
this.loanCollateralAssembler = loanCollateralAssembler;
this.loanSummaryWrapper = loanSummaryWrapper;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
}
public Loan assembleFrom(final JsonCommand command, final AppUser currentUser) {
@@ -113,6 +117,9 @@ public class LoanAssembler {
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
//validate min and max constraint parameters
this.fromApiJsonDeserializer.validateMinMaxConstraintValues(element, loanProduct);
final Fund fund = findFundByIdIfProvided(fundId);
final Staff loanOfficer = findLoanOfficerByIdIfProvided(loanOfficerId);
final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(transactionProcessingStrategyId);
@@ -37,6 +37,8 @@ public class LoanProductData {
// terms
private final CurrencyData currency;
private final BigDecimal principal;
private BigDecimal minPrincipal;
private BigDecimal maxPrincipal;
private final Integer numberOfRepayments;
private final Integer repaymentEvery;
private final EnumOptionData repaymentFrequencyType;
@@ -80,6 +82,8 @@ public class LoanProductData {
final String description = null;
final CurrencyData currency = null;
final BigDecimal principal = null;
final BigDecimal minPrincipal = null;
final BigDecimal maxPrincipal = null;
final BigDecimal tolerance = null;
final Integer numberOfRepayments = null;
final Integer repaymentEvery = null;
@@ -97,7 +101,7 @@ public class LoanProductData {
final Collection<ChargeData> charges = null;
final EnumOptionData accountingType = null;
return new LoanProductData(id, name, description, currency, principal, tolerance, numberOfRepayments, repaymentEvery,
return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, repaymentEvery,
interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, charges, accountingType);
@@ -106,6 +110,8 @@ public class LoanProductData {
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
final String description = null;
final BigDecimal principal = null;
final BigDecimal minPrincipal = null;
final BigDecimal maxPrincipal = null;
final BigDecimal tolerance = null;
final Integer numberOfRepayments = null;
final Integer repaymentEvery = null;
@@ -123,7 +129,7 @@ public class LoanProductData {
final Collection<ChargeData> charges = null;
final EnumOptionData accountingType = null;
return new LoanProductData(id, name, description, currency, principal, tolerance, numberOfRepayments, repaymentEvery,
return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, repaymentEvery,
interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, charges, accountingType);
@@ -135,6 +141,8 @@ public class LoanProductData {
final String description = null;
final CurrencyData currency = CurrencyData.blank();
final BigDecimal principal = null;
final BigDecimal minPrincipal = null;
final BigDecimal maxPrincipal = null;
final BigDecimal tolerance = null;
final Integer numberOfRepayments = null;
final Integer repaymentEvery = null;
@@ -153,7 +161,7 @@ public class LoanProductData {
final Collection<ChargeData> charges = null;
final EnumOptionData accountingType = LoanEnumerations.accountingRuleType(AccountingRuleType.NONE);
return new LoanProductData(id, name, description, currency, principal, tolerance, numberOfRepayments, repaymentEvery,
return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, repaymentEvery,
interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
transactionProcessingStrategyName, charges, accountingType);
@@ -169,8 +177,9 @@ public class LoanProductData {
}
public LoanProductData(final Long id, final String name, final String description, final CurrencyData currency,
final BigDecimal principal, final BigDecimal tolerance, final Integer numberOfRepayments, final Integer repaymentEvery,
final BigDecimal interestRatePerPeriod, final BigDecimal annualInterestRate, final EnumOptionData repaymentFrequencyType,
final BigDecimal principal, final BigDecimal minPrincipal, final BigDecimal maxPrincipal, final BigDecimal tolerance,
final Integer numberOfRepayments, final Integer repaymentEvery, final BigDecimal interestRatePerPeriod,
final BigDecimal annualInterestRate, final EnumOptionData repaymentFrequencyType,
final EnumOptionData interestRateFrequencyType, final EnumOptionData amortizationType, final EnumOptionData interestType,
final EnumOptionData interestCalculationPeriodType, final Long fundId, final String fundName,
final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName,
@@ -180,6 +189,8 @@ public class LoanProductData {
this.description = description;
this.currency = currency;
this.principal = principal;
this.minPrincipal = minPrincipal;
this.maxPrincipal = maxPrincipal;
this.inArrearsTolerance = tolerance;
this.numberOfRepayments = numberOfRepayments;
this.repaymentEvery = repaymentEvery;
@@ -227,6 +238,8 @@ public class LoanProductData {
this.fundName = productData.fundName;
this.principal = productData.principal;
this.minPrincipal = productData.minPrincipal;
this.maxPrincipal = productData.maxPrincipal;
this.inArrearsTolerance = productData.inArrearsTolerance;
this.numberOfRepayments = productData.numberOfRepayments;
this.repaymentEvery = productData.repaymentEvery;
@@ -337,6 +350,14 @@ public class LoanProductData {
return principal;
}
public BigDecimal getMinPrincipal() {
return this.minPrincipal;
}
public BigDecimal getMaxPrincipal() {
return this.maxPrincipal;
}
public BigDecimal getInArrearsTolerance() {
return inArrearsTolerance;
}
@@ -24,6 +24,7 @@ import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
@@ -78,6 +79,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal);
final BigDecimal principal = command.bigDecimalValueOfParameterNamed("principal");
final BigDecimal minPrincipal = command.bigDecimalValueOfParameterNamed("minPrincipal");
final BigDecimal maxPrincipal = command.bigDecimalValueOfParameterNamed("maxPrincipal");
final InterestMethod interestMethod = InterestMethod.fromInt(command.integerValueOfParameterNamed("interestType"));
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command
@@ -95,9 +98,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
final BigDecimal inArrearsTolerance = command.bigDecimalValueOfParameterNamed("inArrearsTolerance");
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, interestRatePerPeriod,
interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery,
repaymentFrequencyType, numberOfRepayments, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType);
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, minPrincipal, maxPrincipal,
interestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod,
repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationMethod, inArrearsTolerance, productCharges,
accountingRuleType);
}
protected LoanProduct() {
@@ -106,6 +110,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
public LoanProduct(final Fund fund, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final String name,
final String description, final MonetaryCurrency currency, final BigDecimal defaultPrincipal,
final BigDecimal defaultMinPrincipal, final BigDecimal defaultMaxPrincipal,
final BigDecimal defaultNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType,
final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery,
@@ -125,9 +130,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.charges = charges;
}
this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery,
repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance);
this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal,
defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod,
interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod,
inArrearsTolerance);
if (accountingRuleType != null) {
this.accountingRule = accountingRuleType.getValue();
@@ -227,4 +233,12 @@ public class LoanProduct extends AbstractPersistable<Long> {
public boolean isAccrualBasedAccountingEnabled() {
return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingRule);
}
public Money getMinPrincipalAmount(){
return this.loanProductRelatedDetail.getMinPrincipal();
}
public Money getMaxPrincipalAmount(){
return this.loanProductRelatedDetail.getMaxPrincipal();
}
}
@@ -20,6 +20,7 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanproduct.exception.NotInMinMaxRangeException;
/**
* LoanRepaymentScheduleDetail encapsulates all the details of a
@@ -33,6 +34,12 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "principal_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal principal;
@Column(name = "min_principal_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal minPrincipal;
@Column(name = "max_principal_amount", scale = 6, precision = 19, nullable = false)
private BigDecimal maxPrincipal;
@Column(name = "nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = false)
private BigDecimal nominalInterestRatePerPeriod;
@@ -80,13 +87,13 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
private BigDecimal inArrearsTolerance;
public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments,
final BigDecimal minPrincipal, final BigDecimal maxPrincipal, final BigDecimal nominalInterestRatePerPeriod,
final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate,
final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod,
final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments,
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance) {
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
return new LoanProductRelatedDetail(currency, principal, minPrincipal, maxPrincipal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType,
numberOfRepayments, amortizationMethod, inArrearsTolerance);
}
@@ -96,6 +103,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
}
public LoanProductRelatedDetail(final MonetaryCurrency currency, final BigDecimal defaultPrincipal,
final BigDecimal defaultMinPrincipal, final BigDecimal defaultMaxPrincipal,
final BigDecimal defaultNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType,
final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery,
@@ -103,6 +111,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance) {
this.currency = currency;
this.principal = defaultPrincipal;
this.minPrincipal = defaultMinPrincipal;
this.maxPrincipal = defaultMaxPrincipal;
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
this.interestPeriodFrequencyType = interestPeriodFrequencyType;
this.annualNominalInterestRate = defaultAnnualNominalInterestRate;
@@ -127,6 +137,14 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return Money.of(this.currency, this.principal);
}
public Money getMinPrincipal() {
return Money.of(this.currency, this.minPrincipal);
}
public Money getMaxPrincipal() {
return Money.of(this.currency, this.maxPrincipal);
}
public Money getInArrearsTolerance() {
return Money.of(this.currency, this.inArrearsTolerance);
}
@@ -208,12 +226,29 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final String localeAsInput = command.locale();
final String minPrincipalParamName = "minPrincipal";
if (command.isChangeInBigDecimalParameterNamed(minPrincipalParamName, this.minPrincipal)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minPrincipalParamName);
actualChanges.put(minPrincipalParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.minPrincipal = newValue;
}
final String maxPrincipalParamName = "maxPrincipal";
if (command.isChangeInBigDecimalParameterNamed(maxPrincipalParamName, this.maxPrincipal)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxPrincipalParamName);
actualChanges.put(maxPrincipalParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.maxPrincipal = newValue;
}
final String principalParamName = "principal";
if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.principal)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
actualChanges.put(principalParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.principal = newValue;
validatePrincipalAmount();
}
final String repaymentEveryParamName = "repaymentEvery";
@@ -294,6 +329,13 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return actualChanges;
}
private void validatePrincipalAmount() {
if(this.principal.compareTo(this.minPrincipal) == -1 || this.principal.compareTo(maxPrincipal) == 1){
final String errorMessage = "The Principal amount " + this.principal + " is invalid. Must be an amount between " + this.minPrincipal + " and " + this.maxPrincipal + " inclusive.";
throw new NotInMinMaxRangeException("loan", "principal.amount", errorMessage, this.principal, this.minPrincipal, this.maxPrincipal);
}
}
private void updateInterestRateDerivedFields(final AprCalculator aprCalculator) {
this.annualNominalInterestRate = aprCalculator.calculateFrom(this.interestPeriodFrequencyType, this.nominalInterestRatePerPeriod);
}
@@ -0,0 +1,19 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanproduct.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when a value is not within minimum and maximum values.
*
*/
public class NotInMinMaxRangeException extends AbstractPlatformDomainRuleException {
public NotInMinMaxRangeException(final String entity, final String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("validation.msg." + entity + "." + postFix + ".is.not.within.min.max.range", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -36,7 +36,7 @@ public final class LoanProductCommandFromApiJsonDeserializer {
* The parameters supported for this command.
*/
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("locale", "name", "description", "fundId",
"currencyCode", "digitsAfterDecimal", "principal", "repaymentEvery", "numberOfRepayments", "repaymentFrequencyType",
"currencyCode", "digitsAfterDecimal", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery", "numberOfRepayments", "repaymentFrequencyType",
"interestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType",
"inArrearsTolerance", "transactionProcessingStrategyId", "charges", "accountingRule",
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(),
@@ -81,9 +81,15 @@ public final class LoanProductCommandFromApiJsonDeserializer {
final Integer digitsAfterDecimal = fromApiJsonHelper.extractIntegerNamed("digitsAfterDecimal", element, Locale.getDefault());
baseDataValidator.reset().parameter("digitsAfterDecimal").value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
final BigDecimal minPrincipal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("minPrincipal", element);
baseDataValidator.reset().parameter("minPrincipal").value(minPrincipal).notNull().positiveAmount();
final BigDecimal maxPrincipal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("maxPrincipal", element);
baseDataValidator.reset().parameter("maxPrincipal").value(maxPrincipal).notNull().positiveAmount();
final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount();
baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount().inMinAndMaxAmountRange(minPrincipal, maxPrincipal);
final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments).notNull().integerGreaterThanZero();
@@ -212,10 +218,33 @@ public final class LoanProductCommandFromApiJsonDeserializer {
final Integer digitsAfterDecimal = fromApiJsonHelper.extractIntegerNamed("digitsAfterDecimal", element, Locale.getDefault());
baseDataValidator.reset().parameter("digitsAfterDecimal").value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
}
String minPrincipalParameterName = "minPrincipal";
BigDecimal minPrincipalAmount = null;
if(fromApiJsonHelper.parameterExists(minPrincipalParameterName, element)){
minPrincipalAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minPrincipalParameterName, element);
}
String maxPrincipalParameterName = "maxPrincipal";
BigDecimal maxPrincipalAmount = null;
if(fromApiJsonHelper.parameterExists(maxPrincipalParameterName, element)){
maxPrincipalAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxPrincipalParameterName, element);
}
//Principal amount is allowed to change only when Min and Max principal amounts are sent in JSON request
//Validate if minPrincipal amount or principal amount is part of JSON request.
if(fromApiJsonHelper.parameterExists(minPrincipalParameterName, element) || fromApiJsonHelper.parameterExists("principal", element)){
baseDataValidator.reset().parameter(minPrincipalParameterName).value(minPrincipalAmount).notNull().positiveAmount();
}
//Validate if maxPrincipal amount or principal amount is part of JSON request.
if(fromApiJsonHelper.parameterExists(maxPrincipalParameterName, element) || fromApiJsonHelper.parameterExists("principal", element)){
baseDataValidator.reset().parameter(maxPrincipalParameterName).value(maxPrincipalAmount).notNull().positiveAmount();
}
if (fromApiJsonHelper.parameterExists("principal", element)) {
final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount();
baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount().inMinAndMaxAmountRange(minPrincipalAmount, maxPrincipalAmount);
}
if (fromApiJsonHelper.parameterExists("inArrearsTolerance", element)) {
@@ -96,7 +96,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
public String loanProductSchema() {
return "lp.id as id, lp.fund_id as fundId, f.name as fundName, lp.loan_transaction_strategy_id as transactionStrategyId, ltps.name as transactionStrategyName, "
+ "lp.name as name, lp.description as description, "
+ "lp.principal_amount as principal, lp.currency_code as currencyCode, lp.currency_digits as currencyDigits, "
+ "lp.principal_amount as principal, lp.min_principal_amount as minPrincipal, lp.max_principal_amount as maxPrincipal, lp.currency_code as currencyCode, lp.currency_digits as currencyDigits, "
+ "lp.nominal_interest_rate_per_period as interestRatePerPeriod, lp.interest_period_frequency_enum as interestRatePerPeriodFreq, "
+ "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod,"
+ "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, "
@@ -130,6 +130,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
currencyNameCode);
final BigDecimal principal = rs.getBigDecimal("principal");
final BigDecimal minPrincipal = rs.getBigDecimal("minPrincipal");
final BigDecimal maxPrincipal = rs.getBigDecimal("maxPrincipal");
final BigDecimal tolerance = rs.getBigDecimal("tolerance");
final Integer numberOfRepayments = JdbcSupport.getInteger(rs, "numberOfRepayments");
@@ -157,10 +159,10 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final EnumOptionData interestCalculationPeriodType = LoanEnumerations
.interestCalculationPeriodType(interestCalculationPeriodTypeId);
return new LoanProductData(id, name, description, currency, principal, tolerance, numberOfRepayments, repaymentEvery,
interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
interestType, interestCalculationPeriodType, fundId, fundName, transactionStrategyId, transactionStrategyName,
this.charges, accountingRuleType);
return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, repaymentEvery, interestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName,
transactionStrategyId, transactionStrategyName, this.charges, accountingRuleType);
}
}
@@ -21,6 +21,8 @@ public class LoanProductRelatedDetailTestHelper {
MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
BigDecimal defaultMinPrincipal = BigDecimal.valueOf(Double.valueOf("100000"));
BigDecimal defaultMaxPrincipal = BigDecimal.valueOf(Double.valueOf("250000"));
// 2% per month, 24% per year
BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
@@ -37,7 +39,7 @@ public class LoanProductRelatedDetailTestHelper {
BigDecimal inArrearsTolerance = BigDecimal.ZERO;
return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
}
@@ -45,6 +47,8 @@ public class LoanProductRelatedDetailTestHelper {
public static LoanProductRelatedDetail createSettingsForEqualInstallmentAmortizationQuarterly() {
MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
BigDecimal defaultMinPrincipal = BigDecimal.valueOf(Double.valueOf("100000"));
BigDecimal defaultMaxPrincipal = BigDecimal.valueOf(Double.valueOf("250000"));
// 2% per month, 24% per year
BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
@@ -61,7 +65,7 @@ public class LoanProductRelatedDetailTestHelper {
BigDecimal inArrearsTolerance = BigDecimal.ZERO;
return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
}
@@ -70,6 +74,8 @@ public class LoanProductRelatedDetailTestHelper {
MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
BigDecimal defaultMinPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
BigDecimal defaultMaxPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
// 2% per month, 24% per year
BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
@@ -85,7 +91,7 @@ public class LoanProductRelatedDetailTestHelper {
BigDecimal inArrearsTolerance = BigDecimal.ZERO;
return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
}
@@ -94,6 +100,8 @@ public class LoanProductRelatedDetailTestHelper {
MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("KSH").withDigitsAfterDecimal(0).build();
BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("15000"));
BigDecimal defaultMinPrincipal = BigDecimal.valueOf(Double.valueOf("15000"));
BigDecimal defaultMaxPrincipal = BigDecimal.valueOf(Double.valueOf("15000"));
// 2% per month, 24% per year
BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
@@ -111,19 +119,20 @@ public class LoanProductRelatedDetailTestHelper {
AmortizationMethod amortizationMethod = AmortizationMethod.EQUAL_PRINCIPAL;
return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal,
defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod,
interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfRepayments, amortizationMethod,
inArrearsTolerance);
}
private static LoanProductRelatedDetail createLoanProductRelatedDetail(MonetaryCurrency currency, BigDecimal defaultPrincipal,
BigDecimal defaultNominalInterestRatePerPeriod, PeriodFrequencyType interestPeriodFrequencyType,
BigDecimal defaultAnnualNominalInterestRate, InterestMethod interestMethod,
BigDecimal defaultMinPrincipal, BigDecimal defaultMaxPrincipal, BigDecimal defaultNominalInterestRatePerPeriod,
PeriodFrequencyType interestPeriodFrequencyType, BigDecimal defaultAnnualNominalInterestRate, InterestMethod interestMethod,
InterestCalculationPeriodMethod interestCalculationPeriodMethod, Integer repayEvery,
PeriodFrequencyType repaymentFrequencyType, Integer defaultNumberOfRepayments, AmortizationMethod amortizationMethod,
BigDecimal inArrearsTolerance) {
return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
}