From 9c8edb45a3a86bb4330afc73a999140f687a5d11 Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Thu, 24 May 2012 01:56:50 +0100 Subject: [PATCH] support update single piece of information for loan product and tidy up validation. --- .../data/command/LoanProductCommand.java | 29 ++ .../global-translations/messages.properties | 20 +- .../messages_fr.properties | 20 +- .../views/administration-view-templates.jsp | 6 +- .../platform/DataValidatorBuilder.java | 133 ++++++ .../platform/api/LoanProductApiResource.java | 34 +- .../ApiDataConversionServiceImpl.java | 2 +- .../platform/loan/domain/LoanProduct.java | 8 +- .../loan/domain/LoanProductRelatedDetail.java | 45 +- .../service/LoanProductCommandValidator.java | 452 ++++++++++-------- ...WritePlatformServiceJpaRepositoryImpl.java | 33 +- .../service/OfficeCommandValidator.java | 84 +--- 12 files changed, 518 insertions(+), 348 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosng/platform/DataValidatorBuilder.java diff --git a/mifosng-data/src/main/java/org/mifosng/data/command/LoanProductCommand.java b/mifosng-data/src/main/java/org/mifosng/data/command/LoanProductCommand.java index 4cc6299bf..1c307bb00 100644 --- a/mifosng-data/src/main/java/org/mifosng/data/command/LoanProductCommand.java +++ b/mifosng-data/src/main/java/org/mifosng/data/command/LoanProductCommand.java @@ -15,13 +15,16 @@ public class LoanProductCommand { private String currencyCode; private Integer digitsAfterDecimal; private BigDecimal principal; + private String principalFormatted; private BigDecimal inArrearsToleranceAmount; + private String inArrearsToleranceAmountFormatted; private Integer repaymentEvery; private Integer repaymentFrequency; private Integer numberOfRepayments; private BigDecimal interestRatePerPeriod; + private String interestRatePerPeriodFormatted; private Integer interestRateFrequencyMethod; private Integer amortizationMethod; private Integer interestMethod; @@ -159,4 +162,30 @@ public class LoanProductCommand { Integer interestCalculationPeriodMethod) { this.interestCalculationPeriodMethod = interestCalculationPeriodMethod; } + + public String getPrincipalFormatted() { + return principalFormatted; + } + + public void setPrincipalFormatted(String principalFormatted) { + this.principalFormatted = principalFormatted; + } + + public String getInterestRatePerPeriodFormatted() { + return interestRatePerPeriodFormatted; + } + + public void setInterestRatePerPeriodFormatted( + String interestRatePerPeriodFormatted) { + this.interestRatePerPeriodFormatted = interestRatePerPeriodFormatted; + } + + public String getInArrearsToleranceAmountFormatted() { + return inArrearsToleranceAmountFormatted; + } + + public void setInArrearsToleranceAmountFormatted( + String inArrearsToleranceAmountFormatted) { + this.inArrearsToleranceAmountFormatted = inArrearsToleranceAmountFormatted; + } } \ No newline at end of file diff --git a/mifosng-individual-lending-app/src/main/webapp/WEB-INF/static/global-translations/messages.properties b/mifosng-individual-lending-app/src/main/webapp/WEB-INF/static/global-translations/messages.properties index c89199b73..8ed555824 100644 --- a/mifosng-individual-lending-app/src/main/webapp/WEB-INF/static/global-translations/messages.properties +++ b/mifosng-individual-lending-app/src/main/webapp/WEB-INF/static/global-translations/messages.properties @@ -242,16 +242,18 @@ form.label.loan.product.interest.rate.calcutated.in.period=Interest calculation validation.msg.product.name.cannot.be.blank=Product name cannot be blank. validation.msg.product.description.exceeds.max.length=The content of the description has exceeded max allowed length of 500 characters. validation.msg.product.currency.digitsAfterDecimal.cannot.be.empty=You must enter a number to represent number of digits after decimal to be supported for product. -validation.msg.product.principal.amount.cannot.be.empty=Loan amount cannot be blank. +validation.msg.product.digitsAfterDecimal.is.not.within.expected.range=Decimal places must be a number between {1} and {2} inclusive. + validation.msg.product.repayment.repaidEvery.cannot.be.empty=Repaid every cannot be blank. +validation.msg.product.repaymentEvery.not.greater.than.zero=Repaid every must be greater than 0. validation.msg.product.number.of.repayments.cannot.be.empty=# of repayments cannot be blank. -validation.msg.product.arrears.tolerance.amount.cannot.be.empty=Arrears tolerance cannot be blank. -validation.msg.product.interest.rate.per.period.amount.cannot.be.empty=Nominal interest rate % cannot be blank. +validation.msg.product.numberOfRepayments.not.greater.than.zero=# of repayments must be greater than 0. + +validation.msg.product.principalFormatted.cannot.be.blank=Loan amount cannot be blank. +validation.msg.product.inArrearsToleranceAmountFormatted.cannot.be.blank=Arrears tolerance cannot be blank. +validation.msg.product.interestRatePerPeriodFormatted.cannot.be.blank=Nominal interest rate % cannot be blank. -validation.msg.product.currency.digitsAfterDecimal.must.be.between.zero.and.six.inclusive=Decimal places must be a number between zero and six inclusive. validation.msg.product.principal.amount.must.be.greater.than.zero=Loan amount must be greater than zero. -validation.msg.product.repayment.repaidEvery.outside.allowed.range=Repaid every must be between {0} and {1} inclusive given the selected repayment frequency type. -validation.msg.product.number.of.repayments.is.out.of.range=# of repayments is out of allowed range for selected repayment frequency type. validation.msg.product.arrears.tolerance.amount.cannot.be.negative=Arrears tolerance cannot be negative. validation.msg.product.interest.rate.per.period.amount.cannot.be.negative=Nominal interest rate % cannot be negative. @@ -270,8 +272,10 @@ form.label.office.openedon=Opened on: form.label.office.externalid=External id: validation.msg.office.name.cannot.be.blank=Office name cannot be blank. -validation.msg.office.parent.cannot.be.blank=You must select a parent office. -validation.msg.office.opening.date.cannot.be.blank=You must choose a date to represent when the office commenced services. +validation.msg.office.parentId.cannot.be.blank=You must select a parent office. +validation.msg.office.parentId.not.greater.than.zero=You must select a parent office. +validation.msg.office.openingDateFormatted.cannot.be.blank=You must choose a date to represent when the office commenced services. +validation.msg.office.externalId.exceeds.max.length=External id exceeds max length of {0} alpha-numeric characters - {1}. dialog.title.configuration.currencies=Currency Configuration form.label.configuration.available.currencies=Available currencies: diff --git a/mifosng-individual-lending-app/src/main/webapp/WEB-INF/static/global-translations/messages_fr.properties b/mifosng-individual-lending-app/src/main/webapp/WEB-INF/static/global-translations/messages_fr.properties index 37a8b21d0..26599d4cc 100644 --- a/mifosng-individual-lending-app/src/main/webapp/WEB-INF/static/global-translations/messages_fr.properties +++ b/mifosng-individual-lending-app/src/main/webapp/WEB-INF/static/global-translations/messages_fr.properties @@ -241,16 +241,18 @@ form.label.loan.product.interest.rate.calcutated.in.period=Interest calculation validation.msg.product.name.cannot.be.blank=Product name cannot be blank.(fr) validation.msg.product.description.exceeds.max.length=The content of the description has exceeded max allowed length of 500 characters.(fr) validation.msg.product.currency.digitsAfterDecimal.cannot.be.empty=You must enter a number to represent number of digits after decimal to be supported for product.(fr) -validation.msg.product.principal.amount.cannot.be.empty=Loan amount cannot be blank.(fr) +validation.msg.product.digitsAfterDecimal.is.not.within.expected.range=Decimal places must be a number between {1} and {2} inclusive. + validation.msg.product.repayment.repaidEvery.cannot.be.empty=Repaid every cannot be blank.(fr) +validation.msg.product.repaymentEvery.not.greater.than.zero=Repaid every must be greater than 0.(fr) validation.msg.product.number.of.repayments.cannot.be.empty=# of repayments cannot be blank.(fr) -validation.msg.product.arrears.tolerance.amount.cannot.be.empty=Arrears tolerance cannot be blank.(fr) -validation.msg.product.interest.rate.per.period.amount.cannot.be.empty=Nominal interest rate % cannot be blank.(fr) +validation.msg.product.numberOfRepayments.not.greater.than.zero=# of repayments must be greater than 0.(fr) + +validation.msg.product.principalFormatted.cannot.be.blank=Loan amount cannot be blank.(fr) +validation.msg.product.inArrearsToleranceAmountFormatted.cannot.be.blank=Arrears tolerance cannot be blank.(fr) +validation.msg.product.interestRatePerPeriodFormatted.cannot.be.blank=Nominal interest rate % cannot be blank.(fr) -validation.msg.product.currency.digitsAfterDecimal.must.be.between.zero.and.six.inclusive=Decimal places must be a number between zero and six inclusive.(fr) validation.msg.product.principal.amount.must.be.greater.than.zero=Loan amount must be greater than zero.(fr) -validation.msg.product.repayment.repaidEvery.outside.allowed.range=Repaid every is out of allowed range for selected repayment frequency type.(fr) -validation.msg.product.number.of.repayments.is.out.of.range=# of repayments is out of allowed range for selected repayment frequency type.(fr) validation.msg.product.arrears.tolerance.amount.cannot.be.negative=Arrears tolerance cannot be negative.(fr) validation.msg.product.interest.rate.per.period.amount.cannot.be.negative=Nominal interest rate % cannot be negative.(fr) @@ -269,8 +271,10 @@ form.label.office.openedon=Opened on:(fr) form.label.office.externalid=External id:(fr) validation.msg.office.name.cannot.be.blank=Office name cannot be blank.(fr) -validation.msg.office.parent.cannot.be.blank=You must select a parent office.(fr) -validation.msg.office.opening.date.cannot.be.blank=You must choose a date to represent when the office commenced services.(fr) +validation.msg.office.parentId.cannot.be.blank=You must select a parent office.(fr) +validation.msg.office.parentId.not.greater.than.zero=You must select a parent office.(fr) +validation.msg.office.openingDateFormatted.cannot.be.blank=You must choose a date to represent when the office commenced services.(fr) +validation.msg.office.externalId.exceeds.max.length=External id exceeds max length of {0} alpha-numeric characters - {1}.(fr) form.label.configuration.available.currencies=Available currencies:(fr) form.label.configuration.allowed.currencies=Allowed currencies:(fr) diff --git a/mifosng-individual-lending-app/src/main/webapp/WEB-INF/views/administration-view-templates.jsp b/mifosng-individual-lending-app/src/main/webapp/WEB-INF/views/administration-view-templates.jsp index c1642c36a..a64ce3ac7 100644 --- a/mifosng-individual-lending-app/src/main/webapp/WEB-INF/views/administration-view-templates.jsp +++ b/mifosng-individual-lending-app/src/main/webapp/WEB-INF/views/administration-view-templates.jsp @@ -57,7 +57,7 @@ - + @@ -89,7 +89,7 @@ - + + diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosng/platform/DataValidatorBuilder.java new file mode 100644 index 000000000..37060c7bc --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/DataValidatorBuilder.java @@ -0,0 +1,133 @@ +package org.mifosng.platform; + +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.mifosng.data.ApiParameterError; + +public class DataValidatorBuilder { + + private final List dataValidationErrors; + private String resource; + private String parameter; + private Object value; + private boolean ignoreNullValue = false; + + public DataValidatorBuilder(List dataValidationErrors) { + this.dataValidationErrors = dataValidationErrors; + } + + public DataValidatorBuilder reset() { + return new DataValidatorBuilder(dataValidationErrors).resource(resource); + } + + public DataValidatorBuilder resource(final String resource) { + this.resource = resource; + return this; + } + + public DataValidatorBuilder parameter(final String parameter) { + this.parameter = parameter; + return this; + } + + public DataValidatorBuilder value(Object value) { + this.value = value; + return this; + } + + public DataValidatorBuilder ignoreIfNull() { + this.ignoreNullValue = true; + return this; + } + + public DataValidatorBuilder notNull() { + if (value == null && !ignoreNullValue) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter).append(".cannot.be.blank"); + StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" cannot be blank."); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter); + dataValidationErrors.add(error); + } + return this; + } + + public DataValidatorBuilder notBlank() { + if (value == null && ignoreNullValue) { + return this; + } + + if (StringUtils.isBlank(value.toString())) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter).append(".cannot.be.blank"); + StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" cannot be blank."); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter); + dataValidationErrors.add(error); + } + return this; + } + + public DataValidatorBuilder notExceedingLengthOf(Integer maxLength) { + if (value == null && ignoreNullValue) { + return this; + } + + if (value != null && value.toString().trim().length() > maxLength) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter).append(".exceeds.max.length"); + StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" exceeds max length of ").append(maxLength).append("."); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter, maxLength, value.toString()); + dataValidationErrors.add(error); + } + return this; + } + + public DataValidatorBuilder inMinMaxRange(Integer min, Integer max) { + if (value == null && ignoreNullValue) { + return this; + } + + if (value != null) { + Integer number = Integer.valueOf(value.toString()); + if (number < min || number > max) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter).append(".is.not.within.expected.range"); + StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" must be between ").append(min).append(" and ").append(max).append("."); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter, number, min, max); + dataValidationErrors.add(error); + } + } + return this; + } + + public DataValidatorBuilder greaterThanZero() { + if (value == null && ignoreNullValue) { + return this; + } + + if (value != null) { + Integer number = Integer.valueOf(value.toString()); + if (number < 1) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter).append(".not.greater.than.zero"); + StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" must be greater than 0."); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter, number, 0); + dataValidationErrors.add(error); + } + } + return this; + } + + public DataValidatorBuilder anyOfNotNull(Object... object) { + boolean hasData = false; + for (Object obj : object) { + if (obj != null) { + hasData = true; + break; + } + } + + if (!hasData) { + StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".no.parameters.for.update"); + StringBuilder defaultEnglishMessage = new StringBuilder("No parameters passed for update."); + ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), "id"); + dataValidationErrors.add(error); + } + return this; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanProductApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanProductApiResource.java index 24291e28c..a9c779aea 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanProductApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanProductApiResource.java @@ -1,6 +1,8 @@ package org.mifosng.platform.api; +import java.math.BigDecimal; import java.util.Collection; +import java.util.Locale; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -16,31 +18,13 @@ import org.mifosng.data.EntityIdentifier; import org.mifosng.data.LoanProductData; import org.mifosng.data.LoanProductList; import org.mifosng.data.command.LoanProductCommand; +import org.mifosng.platform.api.infrastructure.ApiDataConversionService; import org.mifosng.platform.loanproduct.service.LoanProductReadPlatformService; import org.mifosng.platform.loanproduct.service.LoanProductWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -/* - * @see http://viewfromthefringe.blogspot.com/2010/06/versioning-rest-services.html - * @see http://jersey.576304.n2.nabble.com/Restful-Service-Versioning-td6332833.html - * - * Note: Jersey looks for 'requests' starting with 'api' e.g. this resources url is https://[localhost:8080]/api/v1/loanproducts - * Note: Named this class *ApiResource as if you leave it as same class name as older 'resource' class, jersey throws a wobbler. - * Note: no authentication scheme is in place so calls that match api/v1/** are not managed by spring security for now. - * - * 1. resource is versioned here but will move the 'version' part to web.xml when finished. - * 2. using plural of resource e.g. loanproducts rather than loanproduct - * 3. @GET results in list of all loan products being returned (no scoping relevant here) - * 4. Only 'produce'ing JSON by limiting to @Produces({MediaType.APPLICATION_JSON}) - * 5. The java object returned is LoanProductData and LoanProductList when list of products returned. - * These java objects are annotated with @XmlRootElement so JAXB reader/writers will automatically translate to JSON format. - * 6. @GET /loanproducts/{productId} results in loan product of that identifier or appropriate error response - * 7. @POST /loanproducts with correct request body creates new loan product - * 8. @PUT /loanproducts/{productId} updates loan product of that identifier - * 9. Using /resource/template pattern for UI specific function of get 'data with defaults' for new resource. - */ @Path("/v1/loanproducts") @Component @Scope("singleton") @@ -52,6 +36,9 @@ public class LoanProductApiResource { @Autowired private LoanProductWritePlatformService loanProductWritePlatformService; + @Autowired + private ApiDataConversionService apiDataConversionService; + @POST @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @Produces({ MediaType.APPLICATION_JSON}) @@ -102,6 +89,15 @@ public class LoanProductApiResource { @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) @Produces({ MediaType.APPLICATION_JSON }) public Response updateLoanProduct(@PathParam("productId") final Long productId, final LoanProductCommand command) { + + // FIXME - pass in locale through query string or in 'request body' + Locale clientApplicationLocale = Locale.UK; + BigDecimal principal = this.apiDataConversionService.convertFrom(command.getPrincipalFormatted(), "principalFormatted", clientApplicationLocale); + BigDecimal interestRatePerPeriod = this.apiDataConversionService.convertFrom(command.getInterestRatePerPeriodFormatted(), "interestRatePerPeriodFormatted", clientApplicationLocale); + BigDecimal inArrearsToleranceAmount = this.apiDataConversionService.convertFrom(command.getInArrearsToleranceAmountFormatted(), "inArrearsToleranceAmountFormatted", clientApplicationLocale); + command.setPrincipal(principal); + command.setInterestRatePerPeriod(interestRatePerPeriod); + command.setInArrearsToleranceAmount(inArrearsToleranceAmount); command.setId(productId); EntityIdentifier entityIdentifier = this.loanProductWritePlatformService.updateLoanProduct(command); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java index de35718da..356053bae 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/ApiDataConversionServiceImpl.java @@ -54,7 +54,7 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService { } catch (ParseException e) { List dataValidationErrors = new ArrayList(); - ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.number.format", "The parameter " + parameterName + " is invalid.", parameterName, numericalValueFormatted); + ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.number.format", "The parameter " + parameterName + " is invalid for provided locale" + clientApplicationLocale.toString() + ".", parameterName, numericalValueFormatted, clientApplicationLocale); dataValidationErrors.add(error); throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProduct.java index 1e2d731c4..17fe044e5 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProduct.java @@ -133,8 +133,12 @@ public class LoanProduct extends AbstractAuditableCustom { } public void update(LoanProductCommand command) { - this.name = command.getName(); - this.description = command.getDescription(); + if (command.getName() != null) { + this.name = command.getName(); + } + if (command.getDescription() != null) { + this.description = command.getDescription(); + } this.loanProductRelatedDetail.update(command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProductRelatedDetail.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProductRelatedDetail.java index f92e8d599..5f4a2fe11 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProductRelatedDetail.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/domain/LoanProductRelatedDetail.java @@ -173,17 +173,38 @@ public class LoanProductRelatedDetail implements public void update(LoanProductCommand command) { - this.currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimal()); - this.principal = command.getPrincipal(); - this.repayEvery = command.getRepaymentEvery(); - this.repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequency()); - this.numberOfRepayments = command.getNumberOfRepayments(); - this.amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod()); - this.inArrearsTolerance = command.getInArrearsToleranceAmount(); - - this.nominalInterestRatePerPeriod = command.getInterestRatePerPeriod(); - this.interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod()); - this.interestMethod = InterestMethod.fromInt(command.getInterestMethod()); - this.interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodMethod()); + if (command.getCurrencyCode() != null && command.getDigitsAfterDecimal() != null) { + this.currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimal()); + } + if (command.getPrincipal() != null) { + this.principal = command.getPrincipal(); + } + if (command.getRepaymentEvery() != null) { + this.repayEvery = command.getRepaymentEvery(); + } + if (command.getRepaymentFrequency() != null) { + this.repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequency()); + } + if (command.getNumberOfRepayments() != null) { + this.numberOfRepayments = command.getNumberOfRepayments(); + } + if (command.getAmortizationMethod() != null) { + this.amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod()); + } + if (command.getInArrearsToleranceAmount() != null) { + this.inArrearsTolerance = command.getInArrearsToleranceAmount(); + } + if (command.getInterestRatePerPeriod() != null) { + this.nominalInterestRatePerPeriod = command.getInterestRatePerPeriod(); + } + if (command.getInterestRateFrequencyMethod() != null) { + this.interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod()); + } + if (command.getInterestMethod() != null) { + this.interestMethod = InterestMethod.fromInt(command.getInterestMethod()); + } + if (command.getInterestCalculationPeriodMethod() != null) { + this.interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodMethod()); + } } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loanproduct/service/LoanProductCommandValidator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loanproduct/service/LoanProductCommandValidator.java index 91cf5b4b2..83b9e7793 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loanproduct/service/LoanProductCommandValidator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loanproduct/service/LoanProductCommandValidator.java @@ -3,228 +3,278 @@ package org.mifosng.platform.loanproduct.service; import java.util.ArrayList; import java.util.List; -import org.apache.commons.lang.StringUtils; import org.mifosng.data.ApiParameterError; import org.mifosng.data.command.LoanProductCommand; +import org.mifosng.platform.DataValidatorBuilder; import org.mifosng.platform.exceptions.PlatformApiDataValidationException; -import org.mifosng.platform.loan.domain.AmortizationMethod; -import org.mifosng.platform.loan.domain.InterestMethod; -import org.mifosng.platform.loan.domain.PeriodFrequencyType; -// FIXME - There is a lot of common validation across parameters for LoanProduct create/update, loan schedule calculation and submit new loan application flows. -// - reduce duplicator across this validators. public class LoanProductCommandValidator { - public void validateForUpdate(LoanProductCommand command) { + private final LoanProductCommand command; + + public LoanProductCommandValidator(LoanProductCommand command) { + this.command = command; + } + + public void validateForUpdate() { List dataValidationErrors = new ArrayList(); - Long productId = command.getId(); - if (productId == null) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.id.cannot.be.empty", "The parameter id cannot be blank.", "id"); - dataValidationErrors.add(error); - } + DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("product"); - validateRemainingAttributes(command, dataValidationErrors); - } - - public void validateForCreate(LoanProductCommand command) { - List dataValidationErrors = new ArrayList(); + baseDataValidator.reset().parameter("id").value(command.getId()).notNull(); + baseDataValidator.reset().parameter("name").value(command.getName()).ignoreIfNull().notBlank(); + baseDataValidator.reset().parameter("description").value(command.getDescription()).ignoreIfNull().notExceedingLengthOf(500); + baseDataValidator.reset().parameter("externalId").value(command.getExternalId()).ignoreIfNull().notExceedingLengthOf(100); + baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).ignoreIfNull().notBlank(); + baseDataValidator.reset().parameter("digitsAfterDecimal").value(command.getDigitsAfterDecimal()).ignoreIfNull().notNull().inMinMaxRange(0, 6); - validateRemainingAttributes(command, dataValidationErrors); - } - - private void validateRemainingAttributes(LoanProductCommand command, List dataValidationErrors) { + baseDataValidator.reset().parameter("principalFormatted").value(command.getPrincipalFormatted()).ignoreIfNull().notBlank(); + baseDataValidator.reset().parameter("inArrearsToleranceAmountFormatted").value(command.getInArrearsToleranceAmountFormatted()).ignoreIfNull().notBlank(); - if (StringUtils.isBlank(command.getName())) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.name.cannot.be.blank", "The parameter name cannot be blank.", "name"); - dataValidationErrors.add(error); - } + baseDataValidator.reset().parameter("repaymentFrequency").value(command.getRepaymentFrequency()).ignoreIfNull().notNull().inMinMaxRange(0, 3); + baseDataValidator.reset().parameter("repaymentEvery").value(command.getRepaymentEvery()).ignoreIfNull().notNull().greaterThanZero(); + baseDataValidator.reset().parameter("numberOfRepayments").value(command.getNumberOfRepayments()).ignoreIfNull().notNull().greaterThanZero(); - if (command.getDescription() != null) { - if (command.getDescription().length() > 500) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.description.exceeds.max.length", "The parameter description cannot exceed max length of {}.", "description", 500); - dataValidationErrors.add(error); - } - } + baseDataValidator.reset().parameter("interestRatePerPeriodFormatted").value(command.getInterestRatePerPeriodFormatted()).ignoreIfNull().notBlank(); + baseDataValidator.reset().parameter("interestRateFrequencyMethod").value(command.getInterestRateFrequencyMethod()).ignoreIfNull().notNull().inMinMaxRange(0, 3); - if (StringUtils.isBlank(command.getCurrencyCode())) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.currency.cannot.be.empty", "The parameter currencyCode cannot be blank.", "currencyCode"); - dataValidationErrors.add(error); - } + baseDataValidator.reset().parameter("amortizationMethod").value(command.getAmortizationMethod()).ignoreIfNull().notNull().inMinMaxRange(0, 1); + baseDataValidator.reset().parameter("interestMethod").value(command.getInterestMethod()).ignoreIfNull().notNull().inMinMaxRange(0, 1); + baseDataValidator.reset().parameter("interestCalculationPeriodMethod").value(command.getInterestCalculationPeriodMethod()).ignoreIfNull().notNull().inMinMaxRange(0, 1); - if (command.getDigitsAfterDecimal() != null) { - - if (command.getDigitsAfterDecimal() < 0 || command.getDigitsAfterDecimal() > 6) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.currency.digitsAfterDecimal.must.be.between.zero.and.six.inclusive", "The parameter digitsAfterDecimal must be between {0} and {1} inclusive.", "selectedDigitsAfterDecimal", 0, 6); - dataValidationErrors.add(error); - } - } else { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.currency.digitsAfterDecimal.cannot.be.empty", "The parameter digitsAfterDecimal cannot be blank.", "selectedDigitsAfterDecimal"); - dataValidationErrors.add(error); - } - - if (command.getPrincipal() != null) { - if (command.getPrincipal().doubleValue() <= Double.valueOf("0.0")) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.principal.amount.must.be.greater.than.zero", "The parameter amount must be greater than zero.", "amount"); - dataValidationErrors.add(error); - } - } else { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.principal.amount.cannot.be.empty", "The parameter amount cannot be blank.", "amount"); - dataValidationErrors.add(error); - } - - if (command.getRepaymentFrequency() != null) { - PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequency()); - ApiParameterError error = null; - switch (frequencyType) { - case DAYS: - error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 365); - break; - case WEEKS: - error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 52); - break; - case MONTHS: - error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 12); - break; - case YEARS: - error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 2); - break; - default: - error = ApiParameterError.parameterError("validation.msg.product.repayment.frequency.invalid", "The parameter selectedRepaymentFrequencyOption is invalid.", "selectedRepaymentFrequencyOption"); - break; - } - if (error != null) { - dataValidationErrors.add(error); - } - } else { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.repayment.frequency.cannot.be.empty", - "The parameter selectedRepaymentFrequencyOption cannot be blank.", "selectedRepaymentFrequencyOption"); - dataValidationErrors.add(error); - } - - if (command.getNumberOfRepayments() != null) { - if (command.getNumberOfRepayments() < 1 || command.getNumberOfRepayments() > 100) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.number.of.repayments.is.out.of.range", - "The parameter numberOfRepayments must be between {} and {} inclusive.", "numberOfRepayments", 1, 100); - dataValidationErrors.add(error); - } - } else { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.number.of.repayments.cannot.be.empty", - "The parameter numberOfRepayments cannot be empty.", "numberOfRepayments"); - dataValidationErrors.add(error); - } - - if (command.getAmortizationMethod() != null) { - AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod()); - ApiParameterError error = null; - switch (amortizationMethod) { - case EQUAL_PRINCIPAL: - break; - case EQUAL_INSTALLMENTS: - break; - default: - error = ApiParameterError.parameterError("validation.msg.product.amortization.method.invalid", - "The parameter selectedAmortizationMethodOption cannot be empty.", "selectedAmortizationMethodOption"); - break; - } - if (error != null) { - dataValidationErrors.add(error); - } - } else { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.amortization.method.cannot.be.empty", - "The parameter selectedAmortizationMethodOption cannot be empty.", "selectedAmortizationMethodOption"); - dataValidationErrors.add(error); - } - - // refactor in validation of monetary value. - if (command.getInArrearsToleranceAmount() != null) { - if (command.getInArrearsToleranceAmount().doubleValue() < Double.valueOf("0.0")) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.arrears.tolerance.amount.cannot.be.negative", - "The parameter inArrearsTolerance cannot be less than zero.", "inArrearsTolerance"); - dataValidationErrors.add(error); - } - } else { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.arrears.tolerance.amount.cannot.be.empty", - "The parameter inArrearsTolerance cannot be empty.", "inArrearsTolerance"); - dataValidationErrors.add(error); - } - - if (command.getInterestRatePerPeriod()!= null) { - if (command.getInterestRatePerPeriod().doubleValue() < Double.valueOf("0.0")) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.rate.per.period.amount.cannot.be.negative", - "The parameter nominalInterestRate cannot be less than zero.", "nominalInterestRate"); - dataValidationErrors.add(error); - } - } else { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.rate.per.period.amount.cannot.be.empty", - "The parameter nominalInterestRate cannot be empty.", "nominalInterestRate"); - dataValidationErrors.add(error); - } - - // interest rate period type - if (command.getInterestRateFrequencyMethod() != null) { - PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod()); - ApiParameterError error = null; - switch (frequencyType) { - case DAYS: - break; - case WEEKS: - break; - case MONTHS: - break; - case YEARS: - break; - default: - error = ApiParameterError.parameterError("validation.msg.product.interest.frequency.invalid", - "The parameter selectedInterestFrequencyOption is invalid.", "selectedInterestFrequencyOption"); - break; - } - if (error != null) { - dataValidationErrors.add(error); - } - } else { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.frequency.cannot.be.empty", - "The parameter selectedInterestFrequencyOption cannot be empty.", "selectedInterestFrequencyOption"); - dataValidationErrors.add(error); - } - - if (command.getInterestMethod() != null) { - InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod()); - ApiParameterError error = null; - switch (interestMethod) { - case DECLINING_BALANCE: - break; - case FLAT: - break; - default: - error = ApiParameterError.parameterError("validation.msg.product.interest.method.invalid", - "The parameter selectedInterestMethodOption is invalid.", "selectedInterestMethodOption"); - break; - } - if (error != null) { - dataValidationErrors.add(error); - } - } else { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.method.cannot.be.empty", - "The parameter selectedInterestMethodOption cannot be empty.", "selectedInterestMethodOption"); - dataValidationErrors.add(error); - } + baseDataValidator.reset().anyOfNotNull(command.getName(), command.getDescription(), command.getExternalId(), command.getCurrencyCode(), command.getDigitsAfterDecimal(), + command.getPrincipalFormatted(), command.getInArrearsToleranceAmountFormatted(), command.getRepaymentFrequency(), command.getRepaymentEvery(), command.getNumberOfRepayments(), + command.getInterestRatePerPeriodFormatted(), command.getInterestRateFrequencyMethod(), command.getAmortizationMethod(), command.getInterestMethod(), command.getInterestCalculationPeriodMethod()); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); } } - private ApiParameterError validateNumberExistsAndInRange(String errorCodePreFix, String fieldName, Integer value, int min, int max) { - ApiParameterError error = null; - if (value == null) { - error = ApiParameterError.parameterError(errorCodePreFix + ".cannot.be.empty", "The parameter " + fieldName + " cannot be blank.", fieldName); - } else { - if (value < min || value > max) { - error = ApiParameterError.parameterError(errorCodePreFix + ".outside.allowed.range", "The parameter " + fieldName + " must be between {0} and {1} inclusive.", fieldName, min, max); - } + public void validateForCreate() { + + List dataValidationErrors = new ArrayList(); + + DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("product"); + + baseDataValidator.reset().parameter("name").value(command.getName()).notBlank(); + baseDataValidator.reset().parameter("description").value(command.getDescription()).notExceedingLengthOf(500); + baseDataValidator.reset().parameter("externalId").value(command.getExternalId()).notExceedingLengthOf(100); + baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).notBlank(); + baseDataValidator.reset().parameter("digitsAfterDecimal").value(command.getDigitsAfterDecimal()).notNull().inMinMaxRange(0, 6); + + baseDataValidator.reset().parameter("principalFormatted").value(command.getPrincipalFormatted()).notBlank(); + baseDataValidator.reset().parameter("inArrearsToleranceAmountFormatted").value(command.getInArrearsToleranceAmountFormatted()).notBlank(); + + baseDataValidator.reset().parameter("repaymentFrequency").value(command.getRepaymentFrequency()).notNull().inMinMaxRange(0, 3); + baseDataValidator.reset().parameter("repaymentEvery").value(command.getRepaymentEvery()).notNull().greaterThanZero(); + baseDataValidator.reset().parameter("numberOfRepayments").value(command.getNumberOfRepayments()).notNull().greaterThanZero(); + + baseDataValidator.reset().parameter("interestRatePerPeriodFormatted").value(command.getInterestRatePerPeriodFormatted()).notBlank(); + baseDataValidator.reset().parameter("interestRateFrequencyMethod").value(command.getInterestRateFrequencyMethod()).notNull().inMinMaxRange(0, 3); + + baseDataValidator.reset().parameter("amortizationMethod").value(command.getAmortizationMethod()).notNull().inMinMaxRange(0, 1); + baseDataValidator.reset().parameter("interestMethod").value(command.getInterestMethod()).notNull().inMinMaxRange(0, 1); + baseDataValidator.reset().parameter("interestCalculationPeriodMethod").value(command.getInterestCalculationPeriodMethod()).notNull().inMinMaxRange(0, 1); + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); } - return error; } + +// FIXME - commented out old validation for now. test that new validation is appropriate +// private void validateRemainingAttributes(LoanProductCommand command, List dataValidationErrors) { +// +// if (StringUtils.isBlank(command.getName())) { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.name.cannot.be.blank", "The parameter name cannot be blank.", "name"); +// dataValidationErrors.add(error); +// } +// +// if (command.getDescription() != null) { +// if (command.getDescription().length() > 500) { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.description.exceeds.max.length", "The parameter description cannot exceed max length of {}.", "description", 500); +// dataValidationErrors.add(error); +// } +// } +// +// if (StringUtils.isBlank(command.getCurrencyCode())) { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.currency.cannot.be.empty", "The parameter currencyCode cannot be blank.", "currencyCode"); +// dataValidationErrors.add(error); +// } +// +// if (command.getDigitsAfterDecimal() != null) { +// +// if (command.getDigitsAfterDecimal() < 0 || command.getDigitsAfterDecimal() > 6) { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.currency.digitsAfterDecimal.must.be.between.zero.and.six.inclusive", "The parameter digitsAfterDecimal must be between {0} and {1} inclusive.", "selectedDigitsAfterDecimal", 0, 6); +// dataValidationErrors.add(error); +// } +// } else { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.currency.digitsAfterDecimal.cannot.be.empty", "The parameter digitsAfterDecimal cannot be blank.", "selectedDigitsAfterDecimal"); +// dataValidationErrors.add(error); +// } +// +// if (command.getPrincipal() != null) { +// if (command.getPrincipal().doubleValue() <= Double.valueOf("0.0")) { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.principal.amount.must.be.greater.than.zero", "The parameter amount must be greater than zero.", "amount"); +// dataValidationErrors.add(error); +// } +// } else { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.principal.amount.cannot.be.empty", "The parameter amount cannot be blank.", "amount"); +// dataValidationErrors.add(error); +// } +// +// if (command.getRepaymentFrequency() != null) { +// PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequency()); +// ApiParameterError error = null; +// switch (frequencyType) { +// case DAYS: +// error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 365); +// break; +// case WEEKS: +// error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 52); +// break; +// case MONTHS: +// error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 12); +// break; +// case YEARS: +// error = validateNumberExistsAndInRange("validation.msg.product.repayment.repaidEvery", "repaidEvery", command.getRepaymentEvery(), 1, 2); +// break; +// default: +// error = ApiParameterError.parameterError("validation.msg.product.repayment.frequency.invalid", "The parameter selectedRepaymentFrequencyOption is invalid.", "selectedRepaymentFrequencyOption"); +// break; +// } +// if (error != null) { +// dataValidationErrors.add(error); +// } +// } else { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.repayment.frequency.cannot.be.empty", +// "The parameter selectedRepaymentFrequencyOption cannot be blank.", "selectedRepaymentFrequencyOption"); +// dataValidationErrors.add(error); +// } +// +// if (command.getNumberOfRepayments() != null) { +// if (command.getNumberOfRepayments() < 1 || command.getNumberOfRepayments() > 100) { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.number.of.repayments.is.out.of.range", +// "The parameter numberOfRepayments must be between {} and {} inclusive.", "numberOfRepayments", 1, 100); +// dataValidationErrors.add(error); +// } +// } else { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.number.of.repayments.cannot.be.empty", +// "The parameter numberOfRepayments cannot be empty.", "numberOfRepayments"); +// dataValidationErrors.add(error); +// } +// +// if (command.getAmortizationMethod() != null) { +// AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod()); +// ApiParameterError error = null; +// switch (amortizationMethod) { +// case EQUAL_PRINCIPAL: +// break; +// case EQUAL_INSTALLMENTS: +// break; +// default: +// error = ApiParameterError.parameterError("validation.msg.product.amortization.method.invalid", +// "The parameter selectedAmortizationMethodOption cannot be empty.", "selectedAmortizationMethodOption"); +// break; +// } +// if (error != null) { +// dataValidationErrors.add(error); +// } +// } else { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.amortization.method.cannot.be.empty", +// "The parameter selectedAmortizationMethodOption cannot be empty.", "selectedAmortizationMethodOption"); +// dataValidationErrors.add(error); +// } +// +// // refactor in validation of monetary value. +// if (command.getInArrearsToleranceAmount() != null) { +// if (command.getInArrearsToleranceAmount().doubleValue() < Double.valueOf("0.0")) { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.arrears.tolerance.amount.cannot.be.negative", +// "The parameter inArrearsTolerance cannot be less than zero.", "inArrearsTolerance"); +// dataValidationErrors.add(error); +// } +// } else { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.arrears.tolerance.amount.cannot.be.empty", +// "The parameter inArrearsTolerance cannot be empty.", "inArrearsTolerance"); +// dataValidationErrors.add(error); +// } +// +// if (command.getInterestRatePerPeriod()!= null) { +// if (command.getInterestRatePerPeriod().doubleValue() < Double.valueOf("0.0")) { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.rate.per.period.amount.cannot.be.negative", +// "The parameter nominalInterestRate cannot be less than zero.", "nominalInterestRate"); +// dataValidationErrors.add(error); +// } +// } else { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.rate.per.period.amount.cannot.be.empty", +// "The parameter nominalInterestRate cannot be empty.", "nominalInterestRate"); +// dataValidationErrors.add(error); +// } +// +// // interest rate period type +// if (command.getInterestRateFrequencyMethod() != null) { +// PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod()); +// ApiParameterError error = null; +// switch (frequencyType) { +// case DAYS: +// break; +// case WEEKS: +// break; +// case MONTHS: +// break; +// case YEARS: +// break; +// default: +// error = ApiParameterError.parameterError("validation.msg.product.interest.frequency.invalid", +// "The parameter selectedInterestFrequencyOption is invalid.", "selectedInterestFrequencyOption"); +// break; +// } +// if (error != null) { +// dataValidationErrors.add(error); +// } +// } else { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.frequency.cannot.be.empty", +// "The parameter selectedInterestFrequencyOption cannot be empty.", "selectedInterestFrequencyOption"); +// dataValidationErrors.add(error); +// } +// +// if (command.getInterestMethod() != null) { +// InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod()); +// ApiParameterError error = null; +// switch (interestMethod) { +// case DECLINING_BALANCE: +// break; +// case FLAT: +// break; +// default: +// error = ApiParameterError.parameterError("validation.msg.product.interest.method.invalid", +// "The parameter selectedInterestMethodOption is invalid.", "selectedInterestMethodOption"); +// break; +// } +// if (error != null) { +// dataValidationErrors.add(error); +// } +// } else { +// ApiParameterError error = ApiParameterError.parameterError("validation.msg.product.interest.method.cannot.be.empty", +// "The parameter selectedInterestMethodOption cannot be empty.", "selectedInterestMethodOption"); +// dataValidationErrors.add(error); +// } +// +// if (!dataValidationErrors.isEmpty()) { +// throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); +// } +// } +// +// private ApiParameterError validateNumberExistsAndInRange(String errorCodePreFix, String fieldName, Integer value, int min, int max) { +// ApiParameterError error = null; +// if (value == null) { +// error = ApiParameterError.parameterError(errorCodePreFix + ".cannot.be.empty", "The parameter " + fieldName + " cannot be blank.", fieldName); +// } else { +// if (value < min || value > max) { +// error = ApiParameterError.parameterError(errorCodePreFix + ".outside.allowed.range", "The parameter " + fieldName + " must be between {0} and {1} inclusive.", fieldName, min, max); +// } +// } +// return error; +// } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index e838dfead..44849bca3 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -14,6 +14,7 @@ import org.mifosng.platform.loan.domain.InterestMethod; import org.mifosng.platform.loan.domain.LoanProduct; import org.mifosng.platform.loan.domain.LoanProductRepository; import org.mifosng.platform.loan.domain.PeriodFrequencyType; +import org.mifosng.platform.loanschedule.domain.AprCalculator; import org.mifosng.platform.security.PlatformSecurityContext; import org.mifosng.platform.user.domain.AppUser; import org.springframework.beans.factory.annotation.Autowired; @@ -25,11 +26,13 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro private final PlatformSecurityContext context; private final LoanProductRepository loanProductRepository; + private final AprCalculator aprCalculator; @Autowired - public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final LoanProductRepository loanProductRepository) { + public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final LoanProductRepository loanProductRepository, final AprCalculator aprCalculator) { this.context = context; this.loanProductRepository = loanProductRepository; + this.aprCalculator = aprCalculator; } @Transactional @@ -38,8 +41,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro AppUser currentUser = this.context.authenticatedUser(); - LoanProductCommandValidator validator = new LoanProductCommandValidator(); - validator.validateForCreate(command); + LoanProductCommandValidator validator = new LoanProductCommandValidator(command); + validator.validateForCreate(); // assemble LoanProduct from data InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod()); @@ -54,24 +57,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro .fromInt(command.getInterestRateFrequencyMethod()); MonetaryCurrency currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimal()); - - // apr calculator - BigDecimal annualInterestRate = BigDecimal.ZERO; - switch (interestFrequencyType) { - case DAYS: - break; - case WEEKS: - annualInterestRate = command.getInterestRatePerPeriod().multiply(BigDecimal.valueOf(52)); - break; - case MONTHS: - annualInterestRate = command.getInterestRatePerPeriod().multiply(BigDecimal.valueOf(12)); - break; - case YEARS: - annualInterestRate = command.getInterestRatePerPeriod().multiply(BigDecimal.valueOf(1)); - break; - case INVALID: - break; - } + + BigDecimal annualInterestRate = this.aprCalculator.calculateFrom(interestFrequencyType, command.getInterestRatePerPeriod()); LoanProduct loanproduct = new LoanProduct(currentUser.getOrganisation(), command.getName(), command.getDescription(), currency, command.getPrincipal(), @@ -89,8 +76,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro AppUser currentUser = this.context.authenticatedUser(); - LoanProductCommandValidator validator = new LoanProductCommandValidator(); - validator.validateForUpdate(command); + LoanProductCommandValidator validator = new LoanProductCommandValidator(command); + validator.validateForUpdate(); LoanProduct product = this.loanProductRepository.findOne(productThatMatches(currentUser.getOrganisation(), command.getId())); if (product == null) { diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeCommandValidator.java b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeCommandValidator.java index 75ce13e3c..d4ff61c9d 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeCommandValidator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/organisation/service/OfficeCommandValidator.java @@ -3,10 +3,9 @@ package org.mifosng.platform.organisation.service; import java.util.ArrayList; import java.util.List; -import org.apache.commons.lang.StringUtils; -import org.joda.time.LocalDate; import org.mifosng.data.ApiParameterError; import org.mifosng.data.command.OfficeCommand; +import org.mifosng.platform.DataValidatorBuilder; import org.mifosng.platform.exceptions.PlatformApiDataValidationException; public class OfficeCommandValidator { @@ -21,27 +20,12 @@ public class OfficeCommandValidator { List dataValidationErrors = new ArrayList(); - if (StringUtils.isBlank(this.command.getName())) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.name.cannot.be.blank", "The parameter name cannot be blank.", "name"); - dataValidationErrors.add(error); - } + DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("office"); - if (!command.isRootOffice() && (this.command.getParentId() == null || this.command.getParentId().intValue() <= 0)) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.parent.cannot.be.blank", "A valid parentId must be provided.", "parentId"); - dataValidationErrors.add(error); - } - - if (this.command.getOpeningDate() == null) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.opening.date.cannot.be.blank", "The parameter openingDate cannot be blank", "openingDate"); - dataValidationErrors.add(error); - } - - if (StringUtils.isNotBlank(this.command.getExternalId())) { - if (this.command.getExternalId().trim().length() > 100) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.externalId.exceeds.max.length", "The parameter externalId exceeds max length of {0}", "externalId", 100); - dataValidationErrors.add(error); - } - } + baseDataValidator.reset().parameter("name").value(command.getName()).notBlank(); + baseDataValidator.reset().parameter("openingDateFormatted").value(command.getOpeningDateFormatted()).notBlank(); + baseDataValidator.reset().parameter("externalId").value(command.getExternalId()).notExceedingLengthOf(100); + baseDataValidator.reset().parameter("parentId").value(command.getParentId()).notNull().greaterThanZero(); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); @@ -51,60 +35,18 @@ public class OfficeCommandValidator { public void validateForUpdate() { List dataValidationErrors = new ArrayList(); - if (command.getId() == null) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.id.cannot.be.blank", "A valid id must be provided.", "id"); - dataValidationErrors.add(error); - } + DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("office"); - boolean ignoreNulls = true; - - // NOTE: remember for updates that values that are null are ignored from validation and update. - boolean nameHasData = validateParameterNotEmpty(this.command.getName(), "office", "name", ignoreNulls, dataValidationErrors); - boolean openingDateHasData = validateParameterNotEmpty(this.command.getOpeningDateFormatted(), "office", "openingDateFormatted", ignoreNulls, dataValidationErrors); - boolean externalIdHasData = validateParameterNotGreaterThan(100, this.command.getExternalId(), "office", "externalId", ignoreNulls, dataValidationErrors); - boolean parentidHasData = validateParameterNotNull(this.command.getParentId(), "office", "parentid", ignoreNulls, dataValidationErrors); + baseDataValidator.reset().parameter("id").value(command.getId()).notNull(); + baseDataValidator.reset().parameter("name").value(command.getName()).ignoreIfNull().notBlank(); + baseDataValidator.reset().parameter("openingDateFormatted").value(command.getOpeningDateFormatted()).ignoreIfNull().notBlank(); + baseDataValidator.reset().parameter("externalId").value(command.getExternalId()).ignoreIfNull().notExceedingLengthOf(100); + baseDataValidator.reset().parameter("parentId").value(command.getParentId()).ignoreIfNull().notNull().greaterThanZero(); - if (!anyOfTrue(nameHasData, openingDateHasData, externalIdHasData, parentidHasData)) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.office.no.parameters.for.update", "No parameters passed for update.", "id"); - dataValidationErrors.add(error); - } + baseDataValidator.reset().anyOfNotNull(command.getName(), command.getOpeningDateFormatted(), command.getExternalId(), command.getParentId()); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); } } - - private boolean anyOfTrue(boolean nameHasData, boolean openingDateHasData, boolean externalIdHasData, boolean parentidHasData) { - return nameHasData || openingDateHasData || externalIdHasData || parentidHasData; - } - - private boolean validateParameterNotNull(Long value, String resourceName, String parameterName, boolean ignoreNulls, List dataValidationErrors) { - if (value == null && ignoreNulls) return false; - return true; - } - - private boolean validateParameterNotGreaterThan(Integer maxLength, String value, String resourceName, String parameterName, boolean ignoreNulls, List dataValidationErrors) { - if (value == null && ignoreNulls) return false; - - if (this.command.getExternalId().trim().length() > maxLength) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg." + resourceName + "." + parameterName + ".exceeds.max.length", "The parameter " + parameterName + " exceeds max length of " + maxLength + ".", parameterName, maxLength); - dataValidationErrors.add(error); - return true; - } - - return true; - } - - private boolean validateParameterNotEmpty(String value, String resourceName, String parameterName, boolean ignoreNulls, List dataValidationErrors) { - - if (value == null && ignoreNulls) return false; - - if (StringUtils.isBlank(value)) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg." + resourceName + "." + parameterName + ".cannot.be.blank", "The parameter " + parameterName + " cannot be blank.", parameterName); - dataValidationErrors.add(error); - return true; - } - - return true; - } } \ No newline at end of file