diff --git a/mifosng-db/migrations/core_db/V8__add_min_max_constraint_column_to_loan_loanproduct.sql b/mifosng-db/migrations/core_db/V8__add_min_max_constraint_column_to_loan_loanproduct.sql new file mode 100644 index 000000000..0e864a568 --- /dev/null +++ b/mifosng-db/migrations/core_db/V8__add_min_max_constraint_column_to_loan_loanproduct.sql @@ -0,0 +1,37 @@ +ALTER TABLE `m_product_loan` + ADD COLUMN `min_nominal_interest_rate_per_period` DECIMAL(19,6) NOT NULL AFTER `nominal_interest_rate_per_period`, + ADD COLUMN `max_nominal_interest_rate_per_period` DECIMAL(19,6) NOT NULL AFTER `min_nominal_interest_rate_per_period`, + ADD COLUMN `min_number_of_repayments` SMALLINT(5) NOT NULL AFTER `number_of_repayments`, + ADD COLUMN `max_number_of_repayments` SMALLINT(5) NOT NULL AFTER `min_number_of_repayments`; + +ALTER TABLE `m_loan` + ADD COLUMN `min_nominal_interest_rate_per_period` DECIMAL(19,6) NOT NULL AFTER `nominal_interest_rate_per_period`, + ADD COLUMN `max_nominal_interest_rate_per_period` DECIMAL(19,6) NOT NULL AFTER `min_nominal_interest_rate_per_period`, + ADD COLUMN `min_number_of_repayments` SMALLINT(5) NOT NULL AFTER `number_of_repayments`, + ADD COLUMN `max_number_of_repayments` SMALLINT(5) NOT NULL AFTER `min_number_of_repayments`; + +ALTER TABLE `m_loan` + ALTER `min_principal_amount` DROP DEFAULT, + ALTER `max_principal_amount` DROP DEFAULT; +ALTER TABLE `m_loan` + CHANGE COLUMN `min_principal_amount` `min_principal_amount` DECIMAL(19,6) NULL AFTER `principal_amount`, + CHANGE COLUMN `max_principal_amount` `max_principal_amount` DECIMAL(19,6) NULL AFTER `min_principal_amount`, + CHANGE COLUMN `min_nominal_interest_rate_per_period` `min_nominal_interest_rate_per_period` DECIMAL(19,6) NULL AFTER `nominal_interest_rate_per_period`, + CHANGE COLUMN `max_nominal_interest_rate_per_period` `max_nominal_interest_rate_per_period` DECIMAL(19,6) NULL AFTER `min_nominal_interest_rate_per_period`, + CHANGE COLUMN `min_number_of_repayments` `min_number_of_repayments` SMALLINT(5) NULL AFTER `number_of_repayments`, + CHANGE COLUMN `max_number_of_repayments` `max_number_of_repayments` SMALLINT(5) NULL AFTER `min_number_of_repayments`; + +ALTER TABLE `m_product_loan` + ALTER `min_principal_amount` DROP DEFAULT, + ALTER `max_principal_amount` DROP DEFAULT, + ALTER `min_nominal_interest_rate_per_period` DROP DEFAULT, + ALTER `max_nominal_interest_rate_per_period` DROP DEFAULT, + ALTER `min_number_of_repayments` DROP DEFAULT, + ALTER `max_number_of_repayments` DROP DEFAULT; +ALTER TABLE `m_product_loan` + CHANGE COLUMN `min_principal_amount` `min_principal_amount` DECIMAL(19,6) NULL AFTER `principal_amount`, + CHANGE COLUMN `max_principal_amount` `max_principal_amount` DECIMAL(19,6) NULL AFTER `min_principal_amount`, + CHANGE COLUMN `min_nominal_interest_rate_per_period` `min_nominal_interest_rate_per_period` DECIMAL(19,6) NULL AFTER `nominal_interest_rate_per_period`, + CHANGE COLUMN `max_nominal_interest_rate_per_period` `max_nominal_interest_rate_per_period` DECIMAL(19,6) NULL AFTER `min_nominal_interest_rate_per_period`, + CHANGE COLUMN `min_number_of_repayments` `min_number_of_repayments` SMALLINT(5) NULL AFTER `number_of_repayments`, + CHANGE COLUMN `max_number_of_repayments` `max_number_of_repayments` SMALLINT(5) NULL AFTER `min_number_of_repayments`; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java index c5aa63a90..f96d71562 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/api/JsonCommand.java @@ -289,6 +289,19 @@ public final class JsonCommand { } return isChanged; } + + public boolean isChangeInBigDecimalParameterNamedWithNullCheck(final String parameterName, final BigDecimal existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final BigDecimal workingValue = bigDecimalValueOfParameterNamed(parameterName); + if(workingValue == null && existingValue != null){ + isChanged = true; + }else{ + isChanged = differenceExists(existingValue, workingValue); + } + } + return isChanged; + } public BigDecimal bigDecimalValueOfParameterNamed(final String parameterName) { return this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(parameterName, parsedCommand); @@ -303,6 +316,19 @@ public final class JsonCommand { return isChanged; } + public boolean isChangeInIntegerParameterNamedWithNullCheck(final String parameterName, final Integer existingValue) { + boolean isChanged = false; + if (parameterExists(parameterName)) { + final Integer workingValue = integerValueOfParameterNamed(parameterName); + if(workingValue == null && existingValue != null){ + isChanged = true; + }else{ + isChanged = differenceExists(existingValue, workingValue); + } + } + return isChanged; + } + public Integer integerValueOfParameterNamed(final String parameterName) { return this.fromApiJsonHelper.extractIntegerWithLocaleNamed(parameterName, parsedCommand); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java index 2c5453c3d..a0e3cec0f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java @@ -388,6 +388,40 @@ public class DataValidatorBuilder { } return this; } + + public DataValidatorBuilder notLessThanMin(final BigDecimal min) { + if (min != null && value != null){ + BigDecimal amount = BigDecimal.valueOf(Double.valueOf(value.toString())); + if (amount.compareTo(min) == -1) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) + .append(".is.less.than.min"); + StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter) .append(" value ").append(amount) + .append(" must not be less than minimum value ").append(min); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), parameter, amount, min); + dataValidationErrors.add(error); + return this; + } + } + return this; + } + + public DataValidatorBuilder notGreaterThanMax(final BigDecimal max) { + if (max != null && value != null){ + BigDecimal amount = BigDecimal.valueOf(Double.valueOf(value.toString())); + if (amount.compareTo(max) == 1) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) + .append(".is.greater.than.max"); + StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter) .append(" value ").append(amount) + .append(" must not be more than maximum value ").append(max); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), parameter, amount, max); + dataValidationErrors.add(error); + return this; + } + } + return this; + } public DataValidatorBuilder comapareMinAndMaxOfTwoBigDecmimalNos(final BigDecimal min, final BigDecimal max) { if (min != null && max != null) @@ -423,4 +457,40 @@ public class DataValidatorBuilder { } return this; } + + public DataValidatorBuilder notLessThanMin(final Integer min) { + if (value == null && ignoreNullValue) { return this; } + + if (value != null && min != null) { + Integer number = Integer.valueOf(value.toString()); + if (number < min) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) + .append(".is.less.than.min"); + StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter) + .append(" must be greater than minimum value ").append(min); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), parameter, number, min); + dataValidationErrors.add(error); + } + } + return this; + } + + public DataValidatorBuilder notGreaterThanMax(final Integer max) { + if (value == null && ignoreNullValue) { return this; } + + if (value != null && max != null) { + Integer number = Integer.valueOf(value.toString()); + if (number > max) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter) + .append(".is.greater.than.max"); + StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(parameter) + .append(" must be less than maximum value ").append(max); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), parameter, number, max); + dataValidationErrors.add(error); + } + } + return this; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index a1df89754..bc77fb267 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -1321,6 +1321,8 @@ public class Loan extends AbstractPersistable { final BigDecimal maxPrincipal = this.loanRepaymentScheduleDetail.getMaxPrincipal().getAmount(); final BigDecimal inArrearsTolerance = this.loanRepaymentScheduleDetail.getInArrearsTolerance().getAmount(); final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod(); + final BigDecimal minInterestRatePerPeriod = this.loanRepaymentScheduleDetail.getMinNominalInterestRatePerPeriod(); + final BigDecimal maxInterestRatePerPeriod = this.loanRepaymentScheduleDetail.getMaxNominalInterestRatePerPeriod(); final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(); final BigDecimal defaultAnnualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); @@ -1329,13 +1331,16 @@ public class Loan extends AbstractPersistable { final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery(); final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType(); final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments(); + final Integer minNumberOfRepayments = this.loanRepaymentScheduleDetail.getMinNumberOfRepayments(); + final Integer maxNumberOfRepayments = this.loanRepaymentScheduleDetail.getMaxNumberOfRepayments(); final AmortizationMethod amortizationMethod = this.loanRepaymentScheduleDetail.getAmortizationMethod(); final Integer loanTermFrequency = this.termFrequency; final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); final LoanSchedule loanSchedule = new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, minPrincipal, - maxPrincipal, interestRatePerPeriod, interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, - interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, + maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestRatePeriodFrequencyType, + defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, + repaymentPeriodFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, amortizationMethod, loanTermFrequency, loanTermPeriodFrequencyType, setOfLoanCharges(), this.getDisbursementDate(), this.getExpectedFirstRepaymentOnDate(), this.getInterestChargedFromDate(), inArrearsTolerance); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanSchedule.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanSchedule.java index b0b66aa34..da5239349 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanSchedule.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanSchedule.java @@ -30,6 +30,8 @@ public final class LoanSchedule { private final BigDecimal minPrincipal; private final BigDecimal maxPrincipal; private final BigDecimal nominalInterestRatePerPeriod; + private final BigDecimal minNominalInterestRatePerPeriod; + private final BigDecimal maxNominalInterestRatePerPeriod; private final PeriodFrequencyType interestRatePeriodFrequencyType; private final BigDecimal nominalAnnualInterestRate; private final InterestMethod interestMethod; @@ -37,6 +39,8 @@ public final class LoanSchedule { private final Integer repaymentEvery; private final PeriodFrequencyType repaymentPeriodFrequencyType; private final Integer numberOfRepayments; + private final Integer minNumberOfRepayments; + private final Integer maxNumberOfRepayments; private final AmortizationMethod amortizationMethod; private final Integer loanTermFrequency; private final PeriodFrequencyType loanTermPeriodFrequencyType; @@ -49,10 +53,10 @@ public final class LoanSchedule { public LoanSchedule(final LoanScheduleGenerator loanScheduleGenerator, final ApplicationCurrency applicationCurrency, final BigDecimal principal, final BigDecimal minPrincipal, final BigDecimal maxPrincipal, - final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType, + final BigDecimal nominalInterestRatePerPeriod, final BigDecimal minNominalInterestRatePerPeriod, final BigDecimal maxNominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repaymentEvery, - final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments, + final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments, final Integer minNumberOfRepayments, final Integer maxNumberOfRepayments, final AmortizationMethod amortizationMethod, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Set loanCharges, final LocalDate disbursementDate, final LocalDate repaymentStartFromDate, final LocalDate interestChargedFromDate, final BigDecimal inArrearsTolerance) { @@ -62,6 +66,8 @@ public final class LoanSchedule { this.minPrincipal = minPrincipal; this.maxPrincipal = maxPrincipal; this.nominalInterestRatePerPeriod = nominalInterestRatePerPeriod; + this.minNominalInterestRatePerPeriod = minNominalInterestRatePerPeriod; + this.maxNominalInterestRatePerPeriod = maxNominalInterestRatePerPeriod; this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType; this.nominalAnnualInterestRate = nominalAnnualInterestRate; this.interestMethod = interestMethod; @@ -69,6 +75,8 @@ public final class LoanSchedule { this.repaymentEvery = repaymentEvery; this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType; this.numberOfRepayments = numberOfRepayments; + this.minNumberOfRepayments = minNumberOfRepayments; + this.maxNumberOfRepayments = maxNumberOfRepayments; this.amortizationMethod = amortizationMethod; this.loanTermFrequency = loanTermFrequency; this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; @@ -87,9 +95,9 @@ public final class LoanSchedule { public LoanProductRelatedDetail loanProductRelatedDetail() { final MonetaryCurrency currency = new MonetaryCurrency(applicationCurrency.getCode(), applicationCurrency.getDecimalPlaces()); - return LoanProductRelatedDetail.createFrom(currency, principal, minPrincipal, maxPrincipal, nominalInterestRatePerPeriod, + return LoanProductRelatedDetail.createFrom(currency, principal, minPrincipal, maxPrincipal, nominalInterestRatePerPeriod, minNominalInterestRatePerPeriod, maxNominalInterestRatePerPeriod, interestRatePeriodFrequencyType, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, - repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, this.inArrearsTolerance); + repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, amortizationMethod, this.inArrearsTolerance); } public Integer getLoanTermFrequency() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 1b41bf3a0..5bfda11eb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -75,6 +75,8 @@ public class LoanScheduleAssembler { 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 BigDecimal minInterestRatePerPeriod = loanProduct.getMinNominalInterestRatePerPeriod();//Copy from Loan product to Loan + final BigDecimal maxInterestRatePerPeriod = loanProduct.getMaxNominalInterestRatePerPeriod();//Copy from Loan product to Loan final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestRateFrequencyType", element); final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType", @@ -91,6 +93,8 @@ public class LoanScheduleAssembler { final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); final Integer repaymentFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element); final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); + final Integer minNumberOfRepayments = loanProduct.getMinNumberOfRepayments();//Copy from Loan product to Loan + final Integer maxNumberOfRepayments = loanProduct.getMaxNumberOfRepayments();//Copy from Loan product to Loan final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element); final PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyType); final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(amortizationType); @@ -107,9 +111,9 @@ public class LoanScheduleAssembler { final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod); - return new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, + return new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, - repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency, + repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, amortizationMethod, loanTermFrequency, loanTermPeriodFrequencyType, loanCharges, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate, inArrearsTolerance); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index ae6e1610b..82a927dfe 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -57,6 +57,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.note.domain.Note; import org.mifosplatform.portfolio.note.domain.NoteRepository; import org.mifosplatform.useradministration.domain.AppUser; @@ -92,19 +93,21 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final CalendarRepository calendarRepository; private final CalendarInstanceRepository calendarInstanceRepository; + private final LoanProductCommandFromApiJsonDeserializer loanProductCommandFromApiJsonDeserializer; @Autowired public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator, final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, final AprCalculator aprCalculator, final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler, - final CollateralAssembler loanCollateralAssembler, final LoanRepository loanRepository, - final NoteRepository noteRepository, final LoanScheduleCalculationPlatformService calculationPlatformService, - final ClientRepository clientRepository, final LoanProductRepository loanProductRepository, - final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, final LoanSummaryWrapper loanSummaryWrapper, + final CollateralAssembler loanCollateralAssembler, final LoanRepository loanRepository, final NoteRepository noteRepository, + final LoanScheduleCalculationPlatformService calculationPlatformService, final ClientRepository clientRepository, + final LoanProductRepository loanProductRepository, final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory, + final LoanSummaryWrapper loanSummaryWrapper, final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, - final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository) { - this.context = context; + final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository, + final LoanProductCommandFromApiJsonDeserializer loanProductCommandFromApiJsonDeserializer) { + this.context = context; this.fromJsonHelper = fromJsonHelper; this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator; this.fromApiJsonDeserializer = fromApiJsonDeserializer; @@ -122,6 +125,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; this.calendarRepository = calendarRepository; this.calendarInstanceRepository = calendarInstanceRepository; + this.loanProductCommandFromApiJsonDeserializer = loanProductCommandFromApiJsonDeserializer; } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { @@ -199,6 +203,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } final Loan existingLoanApplication = retrieveLoanBy(loanId); + this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), existingLoanApplication.repaymentScheduleDetail(), "loan"); if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified( loanId); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index e6efaf3b0..370511f0c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -42,11 +42,11 @@ 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; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductCommandFromApiJsonDeserializer; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -69,7 +69,7 @@ public class LoanAssembler { private final FromJsonHelper fromApiJsonHelper; private final LoanSummaryWrapper loanSummaryWrapper; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; - private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer; + private final LoanProductCommandFromApiJsonDeserializer loanProductCommandFromApiJsonDeserializer; @Autowired public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository, @@ -79,7 +79,7 @@ public class LoanAssembler { final LoanScheduleAssembler loanScheduleAssembler, final LoanChargeAssembler loanChargeAssembler, final CollateralAssembler loanCollateralAssembler, final LoanSummaryWrapper loanSummaryWrapper, final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, - final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer) { + final LoanProductCommandFromApiJsonDeserializer loanProductCommandFromApiJsonDeserializer) { this.fromApiJsonHelper = fromApiJsonHelper; this.loanProductRepository = loanProductRepository; this.clientRepository = clientRepository; @@ -93,7 +93,7 @@ public class LoanAssembler { this.loanCollateralAssembler = loanCollateralAssembler; this.loanSummaryWrapper = loanSummaryWrapper; this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; - this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.loanProductCommandFromApiJsonDeserializer = loanProductCommandFromApiJsonDeserializer; } public Loan assembleFrom(final JsonCommand command, final AppUser currentUser) { @@ -118,7 +118,7 @@ public class LoanAssembler { if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } //validate min and max constraint parameters - this.fromApiJsonDeserializer.validateMinMaxConstraintValues(element, loanProduct); + this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(element, loanProduct.loanProductRelatedDetail(), "loan"); final Fund fund = findFundByIdIfProvided(fundId); final Staff loanOfficer = findLoanOfficerByIdIfProvided(loanOfficerId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java index 3d6aa4faf..bea254c7e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -57,11 +57,12 @@ import org.springframework.stereotype.Component; public class LoanProductsApiResource { private final Set LOAN_PRODUCT_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "name", "description", "fundId", - "fundName", "currency", "principal", "numberOfRepayments", "repaymentEvery", "repaymentFrequencyType", "interestRatePerPeriod", - "interestRateFrequencyType", "annualInterestRate", "amortizationType", "interestType", "interestCalculationPeriodType", - "inArrearsTolerance", "transactionProcessingStrategyId", "transactionProcessingStrategyName", "charges", "accountingRule", - "accountingMappings", "fundOptions", "currencyOptions", "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", - "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", + "fundName", "currency", "principal", "minPrincipal", "maxPrincipal", "numberOfRepayments", "minNumberOfRepayments", + "maxNumberOfRepayments", "repaymentEvery", "repaymentFrequencyType", "interestRatePerPeriod", "minInterestRatePerPeriod", + "maxInterestRatePerPeriod", "interestRateFrequencyType", "annualInterestRate", "amortizationType", "interestType", + "interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "transactionProcessingStrategyName", + "charges", "accountingRule", "accountingMappings", "fundOptions", "currencyOptions", "repaymentFrequencyTypeOptions", + "interestRateFrequencyTypeOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions", "accountingRuleOptions", "accountingMappingOptions")); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java index e8b84b010..657a3240c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java @@ -40,9 +40,13 @@ public class LoanProductData { private BigDecimal minPrincipal; private BigDecimal maxPrincipal; private final Integer numberOfRepayments; + private final Integer minNumberOfRepayments; + private final Integer maxNumberOfRepayments; private final Integer repaymentEvery; private final EnumOptionData repaymentFrequencyType; private final BigDecimal interestRatePerPeriod; + private final BigDecimal minInterestRatePerPeriod; + private final BigDecimal maxInterestRatePerPeriod; private final EnumOptionData interestRateFrequencyType; private final BigDecimal annualInterestRate; @@ -86,8 +90,12 @@ public class LoanProductData { final BigDecimal maxPrincipal = null; final BigDecimal tolerance = null; final Integer numberOfRepayments = null; + final Integer minNumberOfRepayments = null; + final Integer maxNumberOfRepayments = null; final Integer repaymentEvery = null; final BigDecimal interestRatePerPeriod = null; + final BigDecimal minInterestRatePerPeriod = null; + final BigDecimal maxInterestRatePerPeriod = null; final BigDecimal annualInterestRate = null; final EnumOptionData repaymentFrequencyType = null; final EnumOptionData interestRateFrequencyType = null; @@ -101,8 +109,8 @@ public class LoanProductData { final Collection charges = null; final EnumOptionData accountingType = null; - return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, repaymentEvery, - interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, + return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, + interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, charges, accountingType); } @@ -114,8 +122,12 @@ public class LoanProductData { final BigDecimal maxPrincipal = null; final BigDecimal tolerance = null; final Integer numberOfRepayments = null; + final Integer minNumberOfRepayments = null; + final Integer maxNumberOfRepayments = null; final Integer repaymentEvery = null; final BigDecimal interestRatePerPeriod = null; + final BigDecimal minInterestRatePerPeriod = null; + final BigDecimal maxInterestRatePerPeriod = null; final BigDecimal annualInterestRate = null; final EnumOptionData repaymentFrequencyType = null; final EnumOptionData interestRateFrequencyType = null; @@ -129,8 +141,8 @@ public class LoanProductData { final Collection charges = null; final EnumOptionData accountingType = null; - return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, repaymentEvery, - interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, + return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, + interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, charges, accountingType); } @@ -145,8 +157,12 @@ public class LoanProductData { final BigDecimal maxPrincipal = null; final BigDecimal tolerance = null; final Integer numberOfRepayments = null; + final Integer minNumberOfRepayments = null; + final Integer maxNumberOfRepayments = null; final Integer repaymentEvery = null; final BigDecimal interestRatePerPeriod = null; + final BigDecimal minInterestRatePerPeriod = null; + final BigDecimal maxInterestRatePerPeriod = null; final BigDecimal annualInterestRate = null; final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(PeriodFrequencyType.MONTHS); final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(PeriodFrequencyType.MONTHS); @@ -161,8 +177,9 @@ public class LoanProductData { final Collection charges = null; final EnumOptionData accountingType = LoanEnumerations.accountingRuleType(AccountingRuleType.NONE); - return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, repaymentEvery, - interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, + return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, + minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, + maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId, transactionProcessingStrategyName, charges, accountingType); } @@ -178,12 +195,14 @@ public class LoanProductData { public LoanProductData(final Long id, final String name, final String description, final CurrencyData currency, 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 Integer numberOfRepayments, final Integer minNumberOfRepayments, final Integer maxNumberOfRepayments, + final Integer repaymentEvery, final BigDecimal interestRatePerPeriod, final BigDecimal minInterestRatePerPeriod, + final BigDecimal maxInterestRatePerPeriod, 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, final Collection charges, final EnumOptionData accountingType) { + this.id = id; this.name = name; this.description = description; @@ -193,8 +212,12 @@ public class LoanProductData { this.maxPrincipal = maxPrincipal; this.inArrearsTolerance = tolerance; this.numberOfRepayments = numberOfRepayments; + this.minNumberOfRepayments = minNumberOfRepayments; + this.maxNumberOfRepayments = maxNumberOfRepayments; this.repaymentEvery = repaymentEvery; this.interestRatePerPeriod = interestRatePerPeriod; + this.minInterestRatePerPeriod = minInterestRatePerPeriod; + this.maxInterestRatePerPeriod = maxInterestRatePerPeriod; this.annualInterestRate = annualInterestRate; this.repaymentFrequencyType = repaymentFrequencyType; this.interestRateFrequencyType = interestRateFrequencyType; @@ -227,8 +250,7 @@ public class LoanProductData { final Collection currencyOptions, final List amortizationTypeOptions, final List interestTypeOptions, final List interestCalculationPeriodTypeOptions, final List repaymentFrequencyTypeOptions, final List interestRateFrequencyTypeOptions, - final Collection fundOptions, - final Collection transactionStrategyOptions, + final Collection fundOptions, final Collection transactionStrategyOptions, final Map> accountingMappingOptions, final List accountingRuleOptions, final Map accountingMappings) { this.id = productData.id; @@ -242,8 +264,12 @@ public class LoanProductData { this.maxPrincipal = productData.maxPrincipal; this.inArrearsTolerance = productData.inArrearsTolerance; this.numberOfRepayments = productData.numberOfRepayments; + this.minNumberOfRepayments = productData.minNumberOfRepayments; + this.maxNumberOfRepayments = productData.maxNumberOfRepayments; this.repaymentEvery = productData.repaymentEvery; this.interestRatePerPeriod = productData.interestRatePerPeriod; + this.minInterestRatePerPeriod = productData.minInterestRatePerPeriod; + this.maxInterestRatePerPeriod = productData.maxInterestRatePerPeriod; this.annualInterestRate = productData.annualInterestRate; this.repaymentFrequencyType = productData.repaymentFrequencyType; this.interestRateFrequencyType = productData.interestRateFrequencyType; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index d2f48a1ec..637454933 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -91,16 +91,20 @@ public class LoanProduct extends AbstractPersistable { final PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.fromInt(command .integerValueOfParameterNamed("interestRateFrequencyType")); final BigDecimal interestRatePerPeriod = command.bigDecimalValueOfParameterNamed("interestRatePerPeriod"); + final BigDecimal minInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("minInterestRatePerPeriod"); + final BigDecimal maxInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("maxInterestRatePerPeriod"); final BigDecimal annualInterestRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod); final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery"); final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments"); + final Integer minNumberOfRepayments = command.integerValueOfParameterNamed("minNumberOfRepayments"); + final Integer maxNumberOfRepayments = command.integerValueOfParameterNamed("maxNumberOfRepayments"); final BigDecimal inArrearsTolerance = command.bigDecimalValueOfParameterNamed("inArrearsTolerance"); final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule")); return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, minPrincipal, maxPrincipal, - interestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, - repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationMethod, inArrearsTolerance, productCharges, + interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, + repaymentEvery, repaymentFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType); } @@ -111,10 +115,12 @@ public class LoanProduct extends AbstractPersistable { 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 defaultNominalInterestRatePerPeriod, final BigDecimal defaultMinNominalInterestRatePerPeriod, + final BigDecimal defaultMaxNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfInstallments, + final Integer defaultMinNumberOfInstallments, final Integer defaultMaxNumberOfInstallments, final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List charges, final AccountingRuleType accountingRuleType) { this.fund = fund; @@ -131,9 +137,10 @@ public class LoanProduct extends AbstractPersistable { } this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, - defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, - interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, - inArrearsTolerance); + defaultNominalInterestRatePerPeriod, defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, + interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, + repaymentFrequencyType, defaultNumberOfInstallments, defaultMinNumberOfInstallments, defaultMaxNumberOfInstallments, + amortizationMethod, inArrearsTolerance); if (accountingRuleType != null) { this.accountingRule = accountingRuleType.getValue(); @@ -234,6 +241,10 @@ public class LoanProduct extends AbstractPersistable { return AccountingRuleType.ACCRUAL_BASED.getValue().equals(this.accountingRule); } + public Money getPrincipalAmount(){ + return this.loanProductRelatedDetail.getPrincipal(); + } + public Money getMinPrincipalAmount(){ return this.loanProductRelatedDetail.getMinPrincipal(); } @@ -241,4 +252,32 @@ public class LoanProduct extends AbstractPersistable { public Money getMaxPrincipalAmount(){ return this.loanProductRelatedDetail.getMaxPrincipal(); } + + public BigDecimal getNominalInterestRatePerPeriod() { + return this.loanProductRelatedDetail.getNominalInterestRatePerPeriod(); + } + + public BigDecimal getMinNominalInterestRatePerPeriod() { + return this.loanProductRelatedDetail.getMinNominalInterestRatePerPeriod(); + } + + public BigDecimal getMaxNominalInterestRatePerPeriod(){ + return this.loanProductRelatedDetail.getMaxNominalInterestRatePerPeriod(); + } + + public Integer getNumberOfRepayments(){ + return this.loanProductRelatedDetail.getNumberOfRepayments(); + } + + public Integer getMinNumberOfRepayments() { + return this.loanProductRelatedDetail.getMinNumberOfRepayments(); + } + + public Integer getMaxNumberOfRepayments() { + return this.loanProductRelatedDetail.getMaxNumberOfRepayments(); + } + + public LoanProductRelatedDetail loanProductRelatedDetail(){ + return this.loanProductRelatedDetail; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index e41b3a02e..17750444e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -20,7 +20,6 @@ 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 @@ -35,14 +34,20 @@ 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) + @Column(name = "min_principal_amount", scale = 6, precision = 19, nullable = true) private BigDecimal minPrincipal; - @Column(name = "max_principal_amount", scale = 6, precision = 19, nullable = false) + @Column(name = "max_principal_amount", scale = 6, precision = 19, nullable = true) private BigDecimal maxPrincipal; @Column(name = "nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = false) private BigDecimal nominalInterestRatePerPeriod; + + @Column(name = "min_nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = true) + private BigDecimal minNominalInterestRatePerPeriod; + + @Column(name = "max_nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = true) + private BigDecimal maxNominalInterestRatePerPeriod; // FIXME - move away form JPA ordinal use for enums using just integer - // requires sql patch for existing users of software. @@ -77,6 +82,12 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "number_of_repayments", nullable = false) private Integer numberOfRepayments; + @Column(name = "min_number_of_repayments", nullable = true) + private Integer minNumberOfRepayments; + + @Column(name = "max_number_of_repayments", nullable = true) + private Integer maxNumberOfRepayments; + // FIXME - move away form JPA ordinal use for enums using just integer - // requires sql patch for existing users of software. @Enumerated(EnumType.ORDINAL) @@ -88,14 +99,17 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal, final BigDecimal minPrincipal, final BigDecimal maxPrincipal, final BigDecimal nominalInterestRatePerPeriod, + final BigDecimal minNominalInterestRatePerPeriod, final BigDecimal maxNominalInterestRatePerPeriod, 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) { + final Integer minNumberOfRepayments, final Integer maxNumberOfRepayments, final AmortizationMethod amortizationMethod, + final BigDecimal inArrearsTolerance) { - return new LoanProductRelatedDetail(currency, principal, minPrincipal, maxPrincipal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, + return new LoanProductRelatedDetail(currency, principal, minPrincipal, maxPrincipal, nominalInterestRatePerPeriod, + minNominalInterestRatePerPeriod, maxNominalInterestRatePerPeriod, interestRatePeriodFrequencyType, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType, - numberOfRepayments, amortizationMethod, inArrearsTolerance); + numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, amortizationMethod, inArrearsTolerance); } protected LoanProductRelatedDetail() { @@ -104,16 +118,20 @@ 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 defaultNominalInterestRatePerPeriod, final BigDecimal defaultMinNominalInterestRatePerPeriod, + final BigDecimal defaultMaxNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments, + final Integer defaultMinNumberOfRepayments, final Integer defaultMaxNumberOfRepayments, final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance) { this.currency = currency; this.principal = defaultPrincipal; this.minPrincipal = defaultMinPrincipal; this.maxPrincipal = defaultMaxPrincipal; this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod; + this.minNominalInterestRatePerPeriod = defaultMinNominalInterestRatePerPeriod; + this.maxNominalInterestRatePerPeriod = defaultMaxNominalInterestRatePerPeriod; this.interestPeriodFrequencyType = interestPeriodFrequencyType; this.annualNominalInterestRate = defaultAnnualNominalInterestRate; this.interestMethod = interestMethod; @@ -121,6 +139,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.repayEvery = repayEvery; this.repaymentPeriodFrequencyType = repaymentFrequencyType; this.numberOfRepayments = defaultNumberOfRepayments; + this.minNumberOfRepayments = defaultMinNumberOfRepayments; + this.maxNumberOfRepayments = defaultMaxNumberOfRepayments; this.amortizationMethod = amortizationMethod; if (inArrearsTolerance != null && BigDecimal.ZERO.compareTo(inArrearsTolerance) == 0) { this.inArrearsTolerance = null; @@ -152,7 +172,17 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche public BigDecimal getNominalInterestRatePerPeriod() { return BigDecimal.valueOf(Double.valueOf(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString())); } + + public BigDecimal getMinNominalInterestRatePerPeriod() { + return this.minNominalInterestRatePerPeriod == null ? null : BigDecimal.valueOf(Double.valueOf(this.minNominalInterestRatePerPeriod + .stripTrailingZeros().toString())); + } + public BigDecimal getMaxNominalInterestRatePerPeriod() { + return this.maxNominalInterestRatePerPeriod == null ? null : BigDecimal.valueOf(Double.valueOf(this.maxNominalInterestRatePerPeriod + .stripTrailingZeros().toString())); + } + public PeriodFrequencyType getInterestPeriodFrequencyType() { return interestPeriodFrequencyType; } @@ -183,7 +213,15 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche public Integer getNumberOfRepayments() { return numberOfRepayments; } + + public Integer getMinNumberOfRepayments() { + return minNumberOfRepayments; + } + public Integer getMaxNumberOfRepayments() { + return maxNumberOfRepayments; + } + public AmortizationMethod getAmortizationMethod() { return amortizationMethod; } @@ -227,7 +265,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final String localeAsInput = command.locale(); final String minPrincipalParamName = "minPrincipal"; - if (command.isChangeInBigDecimalParameterNamed(minPrincipalParamName, this.minPrincipal)) { + if (command.isChangeInBigDecimalParameterNamedWithNullCheck(minPrincipalParamName, this.minPrincipal)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minPrincipalParamName); actualChanges.put(minPrincipalParamName, newValue); actualChanges.put("locale", localeAsInput); @@ -235,7 +273,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche } final String maxPrincipalParamName = "maxPrincipal"; - if (command.isChangeInBigDecimalParameterNamed(maxPrincipalParamName, this.maxPrincipal)) { + if (command.isChangeInBigDecimalParameterNamedWithNullCheck(maxPrincipalParamName, this.maxPrincipal)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxPrincipalParamName); actualChanges.put(maxPrincipalParamName, newValue); actualChanges.put("locale", localeAsInput); @@ -248,7 +286,6 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche actualChanges.put(principalParamName, newValue); actualChanges.put("locale", localeAsInput); this.principal = newValue; - validatePrincipalAmount(); } final String repaymentEveryParamName = "repaymentEvery"; @@ -267,6 +304,22 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue); } + final String minNumberOfRepaymentsParamName = "minNumberOfRepayments"; + if (command.isChangeInIntegerParameterNamed(minNumberOfRepaymentsParamName, this.minNumberOfRepayments)) { + final Integer newValue = command.integerValueOfParameterNamed(minNumberOfRepaymentsParamName); + actualChanges.put(minNumberOfRepaymentsParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.minNumberOfRepayments = newValue; + } + + final String maxNumberOfRepaymentsParamName = "maxNumberOfRepayments"; + if (command.isChangeInIntegerParameterNamed(maxNumberOfRepaymentsParamName, this.maxNumberOfRepayments)) { + final Integer newValue = command.integerValueOfParameterNamed(maxNumberOfRepaymentsParamName); + actualChanges.put(maxNumberOfRepaymentsParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.maxNumberOfRepayments = newValue; + } + final String numberOfRepaymentsParamName = "numberOfRepayments"; if (command.isChangeInIntegerParameterNamed(numberOfRepaymentsParamName, this.numberOfRepayments)) { final Integer newValue = command.integerValueOfParameterNamed(numberOfRepaymentsParamName); @@ -291,6 +344,22 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.inArrearsTolerance = newValue; } + final String minInterestRatePerPeriodParamName = "minInterestRatePerPeriod"; + if (command.isChangeInBigDecimalParameterNamedWithNullCheck(minInterestRatePerPeriodParamName, this.minNominalInterestRatePerPeriod)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minInterestRatePerPeriodParamName); + actualChanges.put(minInterestRatePerPeriodParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.minNominalInterestRatePerPeriod = newValue; + } + + final String maxInterestRatePerPeriodParamName = "maxInterestRatePerPeriod"; + if (command.isChangeInBigDecimalParameterNamedWithNullCheck(maxInterestRatePerPeriodParamName, this.maxNominalInterestRatePerPeriod)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxInterestRatePerPeriodParamName); + actualChanges.put(maxInterestRatePerPeriodParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.maxNominalInterestRatePerPeriod = newValue; + } + final String interestRatePerPeriodParamName = "interestRatePerPeriod"; if (command.isChangeInBigDecimalParameterNamed(interestRatePerPeriodParamName, this.nominalInterestRatePerPeriod)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRatePerPeriodParamName); @@ -329,13 +398,6 @@ 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); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java index 64fe5cf63..8d26f6299 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductCommandFromApiJsonDeserializer.java @@ -23,6 +23,7 @@ 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.AccountingRuleType; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -36,9 +37,10 @@ public final class LoanProductCommandFromApiJsonDeserializer { * The parameters supported for this command. */ private final Set supportedParameters = new HashSet(Arrays.asList("locale", "name", "description", "fundId", - "currencyCode", "digitsAfterDecimal", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery", "numberOfRepayments", "repaymentFrequencyType", - "interestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", - "inArrearsTolerance", "transactionProcessingStrategyId", "charges", "accountingRule", + "currencyCode", "digitsAfterDecimal", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery", "numberOfRepayments", + "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentFrequencyType", "interestRatePerPeriod", + "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType", + "interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "charges", "accountingRule", LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), @@ -80,29 +82,104 @@ 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().inMinAndMaxAmountRange(minPrincipal, maxPrincipal); + baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount(); + + String minPrincipalParameterName = "minPrincipal"; + BigDecimal minPrincipalAmount = null; + if(fromApiJsonHelper.parameterExists(minPrincipalParameterName, element)){ + minPrincipalAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minPrincipalParameterName, element); + baseDataValidator.reset().parameter(minPrincipalParameterName).value(minPrincipalAmount).ignoreIfNull().positiveAmount(); + } + + String maxPrincipalParameterName = "maxPrincipal"; + BigDecimal maxPrincipalAmount = null; + if(fromApiJsonHelper.parameterExists(maxPrincipalParameterName, element)){ + maxPrincipalAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxPrincipalParameterName, element); + baseDataValidator.reset().parameter(maxPrincipalParameterName).value(maxPrincipalAmount).ignoreIfNull().positiveAmount(); + } + + if (maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) != -1) { + if (minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter(maxPrincipalParameterName).value(maxPrincipalAmount).notLessThanMin(minPrincipalAmount); + if(minPrincipalAmount.compareTo(maxPrincipalAmount) <= 0){ + baseDataValidator.reset().parameter("principal").value(principal).inMinAndMaxAmountRange(minPrincipalAmount, maxPrincipalAmount); + } + }else{ + baseDataValidator.reset().parameter("principal").value(principal).notGreaterThanMax(maxPrincipalAmount); + } + }else if (minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter("principal").value(principal).notLessThanMin(minPrincipalAmount); + } final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments).notNull().integerGreaterThanZero(); + String minNumberOfRepaymentsParameterName = "minNumberOfRepayments"; + Integer minNumberOfRepayments = null; + if(fromApiJsonHelper.parameterExists(minNumberOfRepaymentsParameterName, element)){ + minNumberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(minNumberOfRepaymentsParameterName, element); + baseDataValidator.reset().parameter(minNumberOfRepaymentsParameterName).value(minNumberOfRepayments).ignoreIfNull().integerGreaterThanZero(); + } + + String maxNumberOfRepaymentsParameterName = "maxNumberOfRepayments"; + Integer maxNumberOfRepayments = null; + if(fromApiJsonHelper.parameterExists(maxNumberOfRepaymentsParameterName, element)){ + maxNumberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(maxNumberOfRepaymentsParameterName, element); + baseDataValidator.reset().parameter(maxNumberOfRepaymentsParameterName).value(maxNumberOfRepayments).ignoreIfNull().integerGreaterThanZero(); + } + + if(maxNumberOfRepayments != null && maxNumberOfRepayments.compareTo(0) == 1){ + if(minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1){ + baseDataValidator.reset().parameter(maxNumberOfRepaymentsParameterName).value(maxNumberOfRepayments).notLessThanMin(minNumberOfRepayments); + if(minNumberOfRepayments.compareTo(maxNumberOfRepayments) <= 0){ + baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments).inMinMaxRange(minNumberOfRepayments, maxNumberOfRepayments); + } + }else{ + baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments).notGreaterThanMax(maxNumberOfRepayments); + } + }else if(minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1){ + baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments).notLessThanMin(minNumberOfRepayments); + } + final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); baseDataValidator.reset().parameter("repaymentEvery").value(repaymentEvery).notNull().integerGreaterThanZero(); final Integer repaymentFrequencyType = fromApiJsonHelper .extractIntegerNamed("repaymentFrequencyType", element, Locale.getDefault()); baseDataValidator.reset().parameter("repaymentFrequencyType").value(repaymentFrequencyType).notNull().inMinMaxRange(0, 3); - + final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount(); + String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod"; + BigDecimal minInterestRatePerPeriod = null; + if(fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)){ + minInterestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName, element); + baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull().zeroOrPositiveAmount(); + } + + String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod"; + BigDecimal maxInterestRatePerPeriod = null; + if(fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)){ + maxInterestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName, element); + baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull().zeroOrPositiveAmount(); + } + + if(maxInterestRatePerPeriod != null && maxInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1){ + if(minInterestRatePerPeriod != null && minInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1){ + baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).notLessThanMin(minInterestRatePerPeriod); + if(minInterestRatePerPeriod.compareTo(maxInterestRatePerPeriod) <= 0){ + baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).inMinAndMaxAmountRange(minInterestRatePerPeriod, maxInterestRatePerPeriod); + } + }else{ + baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notGreaterThanMax(maxInterestRatePerPeriod); + } + }else if(minInterestRatePerPeriod != null && minInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1){ + baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notLessThanMin(minInterestRatePerPeriod); + } + final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerNamed("interestRateFrequencyType", element, Locale.getDefault()); baseDataValidator.reset().parameter("interestRateFrequencyType").value(interestRateFrequencyType).notNull().inMinMaxRange(0, 3); @@ -223,28 +300,19 @@ public final class LoanProductCommandFromApiJsonDeserializer { BigDecimal minPrincipalAmount = null; if(fromApiJsonHelper.parameterExists(minPrincipalParameterName, element)){ minPrincipalAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minPrincipalParameterName, element); + baseDataValidator.reset().parameter(minPrincipalParameterName).value(minPrincipalAmount).ignoreIfNull().positiveAmount(); } 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(); + baseDataValidator.reset().parameter(maxPrincipalParameterName).value(maxPrincipalAmount).ignoreIfNull().positiveAmount(); } if (fromApiJsonHelper.parameterExists("principal", element)) { final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); - baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount().inMinAndMaxAmountRange(minPrincipalAmount, maxPrincipalAmount); + baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount(); } if (fromApiJsonHelper.parameterExists("inArrearsTolerance", element)) { @@ -252,11 +320,39 @@ public final class LoanProductCommandFromApiJsonDeserializer { baseDataValidator.reset().parameter("inArrearsTolerance").value(inArrearsTolerance).ignoreIfNull().zeroOrPositiveAmount(); } - if (fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) { - final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); - baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount(); + String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod"; + BigDecimal minInterestRatePerPeriod = null; + if (fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) { + minInterestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName, element); + baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull().zeroOrPositiveAmount(); } + String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod"; + BigDecimal maxInterestRatePerPeriod = null; + if (fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)) { + maxInterestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName, element); + baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull().zeroOrPositiveAmount(); + } + + if (fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) { + final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); + baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().positiveAmount(); + } + + String minNumberOfRepaymentsParameterName = "minNumberOfRepayments"; + Integer minNumberOfRepayments = null; + if (fromApiJsonHelper.parameterExists(minNumberOfRepaymentsParameterName, element)) { + minNumberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(minNumberOfRepaymentsParameterName, element); + baseDataValidator.reset().parameter(minNumberOfRepaymentsParameterName).value(minNumberOfRepayments).ignoreIfNull().integerGreaterThanZero(); + } + + String maxNumberOfRepaymentsParameterName = "maxNumberOfRepayments"; + Integer maxNumberOfRepayments = null; + if (fromApiJsonHelper.parameterExists(maxNumberOfRepaymentsParameterName, element)) { + maxNumberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(maxNumberOfRepaymentsParameterName, element); + baseDataValidator.reset().parameter(maxNumberOfRepaymentsParameterName).value(maxNumberOfRepayments).ignoreIfNull().integerGreaterThanZero(); + } + if (fromApiJsonHelper.parameterExists("numberOfRepayments", element)) { final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); baseDataValidator.reset().parameter("numberOfRepayments").value(numberOfRepayments).notNull().integerGreaterThanZero(); @@ -352,6 +448,180 @@ public final class LoanProductCommandFromApiJsonDeserializer { throwExceptionIfValidationWarningsExist(dataValidationErrors); } + public void validateMinMaxConstraints(final JsonElement element, final LoanProductRelatedDetail loanProductRelatedDetail, final String resource){ + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(resource); + + validatePrincipalMinMaxConstraint(element, loanProductRelatedDetail, baseDataValidator); + + validateNumberOfRepaymentsMinMaxConstraint(element, loanProductRelatedDetail, baseDataValidator); + + validateNominalInterestRatePerPeriodMinMaxConstraint(element, loanProductRelatedDetail, baseDataValidator); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void validatePrincipalMinMaxConstraint(final JsonElement element, final LoanProductRelatedDetail loanProductRelatedDetail, final DataValidatorBuilder baseDataValidator) { + + boolean principalUpdated = false; + boolean minPrincipalUpdated = false; + boolean maxPrincipalUpdated = false; + String principalParameterName = "principal"; + BigDecimal principalAmount = null; + if (fromApiJsonHelper.parameterExists(principalParameterName, element)) { + principalAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element); + principalUpdated = true; + }else{ + principalAmount = loanProductRelatedDetail.getPrincipal().getAmount(); + } + + String minPrincipalParameterName = "minPrincipal"; + BigDecimal minPrincipalAmount = null; + if(fromApiJsonHelper.parameterExists(minPrincipalParameterName, element)){ + minPrincipalAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minPrincipalParameterName, element); + minPrincipalUpdated = true; + }else{ + minPrincipalAmount = loanProductRelatedDetail.getMinPrincipal().getAmount(); + } + + String maxPrincipalParameterName = "maxPrincipal"; + BigDecimal maxPrincipalAmount = null; + if(fromApiJsonHelper.parameterExists(maxPrincipalParameterName, element)){ + maxPrincipalAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxPrincipalParameterName, element); + maxPrincipalUpdated = true; + }else{ + maxPrincipalAmount = loanProductRelatedDetail.getMaxPrincipal().getAmount(); + } + + if(minPrincipalUpdated){ + baseDataValidator.reset().parameter(minPrincipalParameterName).value(minPrincipalAmount).notGreaterThanMax(maxPrincipalAmount); + } + + if(maxPrincipalUpdated){ + baseDataValidator.reset().parameter(maxPrincipalParameterName).value(maxPrincipalAmount).notLessThanMin(minPrincipalAmount); + } + + if((principalUpdated || minPrincipalUpdated || maxPrincipalUpdated)){ + + if((minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) == 1) && (maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) == 1)){ + baseDataValidator.reset().parameter(principalParameterName).value(principalAmount).inMinAndMaxAmountRange(minPrincipalAmount, maxPrincipalAmount); + }else{ + if(minPrincipalAmount != null && minPrincipalAmount.compareTo(BigDecimal.ZERO) == 1){ + baseDataValidator.reset().parameter(principalParameterName).value(principalAmount).notLessThanMin(minPrincipalAmount); + }else if(maxPrincipalAmount != null && maxPrincipalAmount.compareTo(BigDecimal.ZERO) == 1){ + baseDataValidator.reset().parameter(principalParameterName).value(principalAmount).notGreaterThanMax(maxPrincipalAmount); + } + } + } + } + + private void validateNumberOfRepaymentsMinMaxConstraint(final JsonElement element, final LoanProductRelatedDetail loanProductRelatedDetail, final DataValidatorBuilder baseDataValidator) { + boolean numberOfRepaymentsUpdated = false; + boolean minNumberOfRepaymentsUpdated = false; + boolean maxNumberOfRepaymentsUpdated = false; + + String numberOfRepaymentsParameterName = "numberOfRepayments"; + Integer numberOfRepayments = null; + if (fromApiJsonHelper.parameterExists(numberOfRepaymentsParameterName, element)) { + numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName, element); + numberOfRepaymentsUpdated = true; + }else{ + numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); + } + + String minNumberOfRepaymentsParameterName = "minNumberOfRepayments"; + Integer minNumberOfRepayments = null; + if (fromApiJsonHelper.parameterExists(minNumberOfRepaymentsParameterName, element)) { + minNumberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(minNumberOfRepaymentsParameterName, element); + minNumberOfRepaymentsUpdated = true; + }else{ + minNumberOfRepayments = loanProductRelatedDetail.getMinNumberOfRepayments(); + } + + String maxNumberOfRepaymentsParameterName = "maxNumberOfRepayments"; + Integer maxNumberOfRepayments = null; + if (fromApiJsonHelper.parameterExists(maxNumberOfRepaymentsParameterName, element)) { + maxNumberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(maxNumberOfRepaymentsParameterName, element); + maxNumberOfRepaymentsUpdated = true; + }else{ + maxNumberOfRepayments = loanProductRelatedDetail.getMaxNumberOfRepayments(); + } + + if(minNumberOfRepaymentsUpdated){ + baseDataValidator.reset().parameter(minNumberOfRepaymentsParameterName).value(minNumberOfRepayments).ignoreIfNull().notGreaterThanMax(maxNumberOfRepayments); + } + + if(maxNumberOfRepaymentsUpdated){ + baseDataValidator.reset().parameter(maxNumberOfRepaymentsParameterName).value(maxNumberOfRepayments).notLessThanMin(minNumberOfRepayments); + } + + if(numberOfRepaymentsUpdated || minNumberOfRepaymentsUpdated || maxNumberOfRepaymentsUpdated){ + if(maxNumberOfRepayments != null && maxNumberOfRepayments.compareTo(0) == 1){ + if(minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1){ + baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments).inMinMaxRange(minNumberOfRepayments, maxNumberOfRepayments); + }else{ + baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments).notGreaterThanMax(maxNumberOfRepayments); + } + }else if(minNumberOfRepayments != null && minNumberOfRepayments.compareTo(0) == 1){ + baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments).notLessThanMin(minNumberOfRepayments); + } + } + } + + private void validateNominalInterestRatePerPeriodMinMaxConstraint(final JsonElement element, final LoanProductRelatedDetail loanProductRelatedDetail, final DataValidatorBuilder baseDataValidator) { + + boolean iRPUpdated = false; + boolean minIRPUpdated = false; + boolean maxIRPUpdated = false; + String interestRatePerPeriodParameterName = "interestRatePerPeriod"; + BigDecimal interestRatePerPeriod = null; + + if (fromApiJsonHelper.parameterExists(interestRatePerPeriodParameterName, element)) { + interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRatePerPeriodParameterName, element); + iRPUpdated = true; + }else{ + interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); + } + + String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod"; + BigDecimal minInterestRatePerPeriod = null; + if (fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) { + minInterestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName, element); + minIRPUpdated = true; + }else{ + minInterestRatePerPeriod = loanProductRelatedDetail.getMinNominalInterestRatePerPeriod(); + } + + String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod"; + BigDecimal maxInterestRatePerPeriod = null; + if (fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)) { + maxInterestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName, element); + maxIRPUpdated = true; + }else{ + maxInterestRatePerPeriod = loanProductRelatedDetail.getMaxNominalInterestRatePerPeriod(); + } + + if(minIRPUpdated){ + baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull().notGreaterThanMax(maxInterestRatePerPeriod); + } + + if(maxIRPUpdated){ + baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull().notLessThanMin(minInterestRatePerPeriod); + } + + if(iRPUpdated || minIRPUpdated || maxIRPUpdated){ + if(maxInterestRatePerPeriod != null){ + if(minInterestRatePerPeriod != null){ + baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).inMinAndMaxAmountRange(minInterestRatePerPeriod, maxInterestRatePerPeriod); + }else{ + baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notGreaterThanMax(maxInterestRatePerPeriod); + } + }else if(minInterestRatePerPeriod != null){ + baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notLessThanMin(minInterestRatePerPeriod); + } + } + } + private boolean isCashBasedAccounting(final Integer accountingRuleType) { return AccountingRuleType.CASH_BASED.getValue().equals(accountingRuleType); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index a464026ce..118c8001c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -97,9 +97,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo 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.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.nominal_interest_rate_per_period as interestRatePerPeriod, lp.min_nominal_interest_rate_per_period as minInterestRatePerPeriod, lp.max_nominal_interest_rate_per_period as maxInterestRatePerPeriod, 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, " + + "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, " + "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, " + "lp.accounting_type as accountingType, " + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol " @@ -135,12 +135,16 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final BigDecimal tolerance = rs.getBigDecimal("tolerance"); final Integer numberOfRepayments = JdbcSupport.getInteger(rs, "numberOfRepayments"); + final Integer minNumberOfRepayments = JdbcSupport.getInteger(rs, "minNumberOfRepayments"); + final Integer maxNumberOfRepayments = JdbcSupport.getInteger(rs, "maxNumberOfRepayments"); final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaidEvery"); final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType"); final EnumOptionData accountingRuleType = LoanEnumerations.accountingRuleType(accountingRuleId); final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod"); + final BigDecimal minInterestRatePerPeriod = rs.getBigDecimal("minInterestRatePerPeriod"); + final BigDecimal maxInterestRatePerPeriod = rs.getBigDecimal("maxInterestRatePerPeriod"); final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate"); final int repaymentFrequencyTypeId = JdbcSupport.getInteger(rs, "repaymentPeriodFrequency"); @@ -160,7 +164,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo .interestCalculationPeriodType(interestCalculationPeriodTypeId); return new LoanProductData(id, name, description, currency, principal, minPrincipal, maxPrincipal, tolerance, - numberOfRepayments, repaymentEvery, interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, + numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, + minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName, transactionStrategyId, transactionStrategyName, this.charges, accountingRuleType); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index 83b791155..b8d063b3c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -137,6 +137,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro final LoanProduct product = this.loanProductRepository.findOne(loanProductId); if (product == null) { throw new LoanProductNotFoundException(loanProductId); } + this.fromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), product.loanProductRelatedDetail(), "loanproduct"); + final Map changes = product.update(command, this.aprCalculator); if (changes.containsKey("fundId")) { diff --git a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java index 84558cc8b..9b622af92 100644 --- a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java +++ b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java @@ -26,6 +26,8 @@ public class LoanProductRelatedDetailTestHelper { // 2% per month, 24% per year BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2")); + BigDecimal defaultMinNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("1")); + BigDecimal defaultMaxNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("10")); PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS; BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24")); @@ -35,13 +37,17 @@ public class LoanProductRelatedDetailTestHelper { PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS; Integer defaultNumberOfRepayments = Integer.valueOf(4); + Integer defaultMinNumberOfRepayments = Integer.valueOf(2); + Integer defaultMaxNumberOfRepayments = Integer.valueOf(6); AmortizationMethod amortizationMethod = AmortizationMethod.EQUAL_PRINCIPAL; BigDecimal inArrearsTolerance = BigDecimal.ZERO; - return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, - defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, - defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance); + return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, + defaultNominalInterestRatePerPeriod, defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, + interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, + repaymentFrequencyType, defaultNumberOfRepayments, defaultMinNumberOfRepayments, defaultMaxNumberOfRepayments, + amortizationMethod, inArrearsTolerance); } public static LoanProductRelatedDetail createSettingsForEqualInstallmentAmortizationQuarterly() { @@ -52,6 +58,8 @@ public class LoanProductRelatedDetailTestHelper { // 2% per month, 24% per year BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2")); + BigDecimal defaultMinNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2")); + BigDecimal defaultMaxNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2")); PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS; BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24")); @@ -61,13 +69,17 @@ public class LoanProductRelatedDetailTestHelper { PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS; Integer defaultNumberOfRepayments = Integer.valueOf(4); + Integer defaultMinNumberOfRepayments = Integer.valueOf(4); + Integer defaultMaxNumberOfRepayments = Integer.valueOf(4); AmortizationMethod amortizationMethod = AmortizationMethod.EQUAL_INSTALLMENTS; BigDecimal inArrearsTolerance = BigDecimal.ZERO; - return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, - defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, - defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance); + return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, + defaultNominalInterestRatePerPeriod, defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, + interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, + repaymentFrequencyType, defaultNumberOfRepayments, defaultMinNumberOfRepayments, defaultMaxNumberOfRepayments, + amortizationMethod, inArrearsTolerance); } public static LoanProductRelatedDetail createSettingsForFlatQuarterly(final AmortizationMethod amortizationMethod) { @@ -79,6 +91,8 @@ public class LoanProductRelatedDetailTestHelper { // 2% per month, 24% per year BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2")); + BigDecimal defaultMinNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2")); + BigDecimal defaultMaxNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2")); PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS; BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24")); @@ -88,12 +102,16 @@ public class LoanProductRelatedDetailTestHelper { PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS; Integer defaultNumberOfRepayments = Integer.valueOf(4); + Integer defaultMinNumberOfRepayments = Integer.valueOf(4); + Integer defaultMaxNumberOfRepayments = Integer.valueOf(4); BigDecimal inArrearsTolerance = BigDecimal.ZERO; - return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, - defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, - defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance); + return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, + defaultNominalInterestRatePerPeriod, defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, + interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, + repaymentFrequencyType, defaultNumberOfRepayments, defaultMinNumberOfRepayments, defaultMaxNumberOfRepayments, + amortizationMethod, inArrearsTolerance); } public static LoanProductRelatedDetail createSettingsForIrregularFlatEveryFourMonths() { @@ -105,6 +123,8 @@ public class LoanProductRelatedDetailTestHelper { // 2% per month, 24% per year BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2")); + BigDecimal defaultMinNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2")); + BigDecimal defaultMaxNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("4")); PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS; BigDecimal defaultAnnualNominalInterestRate = BigDecimal.valueOf(Double.valueOf("24")); @@ -114,26 +134,31 @@ public class LoanProductRelatedDetailTestHelper { PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS; Integer defaultNumberOfRepayments = Integer.valueOf(2); + Integer defaultMinNumberOfRepayments = Integer.valueOf(2); + Integer defaultMaxNumberOfRepayments = Integer.valueOf(2); BigDecimal inArrearsTolerance = BigDecimal.ZERO; AmortizationMethod amortizationMethod = AmortizationMethod.EQUAL_PRINCIPAL; return createLoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, - defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, - interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfRepayments, amortizationMethod, + defaultNominalInterestRatePerPeriod, defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, + interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfRepayments, defaultMinNumberOfRepayments, defaultMaxNumberOfRepayments, amortizationMethod, inArrearsTolerance); } private static LoanProductRelatedDetail createLoanProductRelatedDetail(MonetaryCurrency currency, BigDecimal defaultPrincipal, BigDecimal defaultMinPrincipal, BigDecimal defaultMaxPrincipal, BigDecimal defaultNominalInterestRatePerPeriod, + BigDecimal defaultMinNominalInterestRatePerPeriod, BigDecimal defaultMaxNominalInterestRatePerPeriod, PeriodFrequencyType interestPeriodFrequencyType, BigDecimal defaultAnnualNominalInterestRate, InterestMethod interestMethod, InterestCalculationPeriodMethod interestCalculationPeriodMethod, Integer repayEvery, - PeriodFrequencyType repaymentFrequencyType, Integer defaultNumberOfRepayments, AmortizationMethod amortizationMethod, - BigDecimal inArrearsTolerance) { + PeriodFrequencyType repaymentFrequencyType, Integer defaultNumberOfRepayments, Integer defaultMinNumberOfRepayments, + Integer defaultMaxNumberOfRepayments, AmortizationMethod amortizationMethod, BigDecimal inArrearsTolerance) { - return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, - defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, - defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance); + return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultMinPrincipal, defaultMaxPrincipal, + defaultNominalInterestRatePerPeriod, defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, + interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, + repaymentFrequencyType, defaultNumberOfRepayments, defaultMinNumberOfRepayments, defaultMaxNumberOfRepayments, + amortizationMethod, inArrearsTolerance); } } \ No newline at end of file