API-level modelling for ranges for charges.
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
index f04545b..1adcc04 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/client/PortfolioManager.java
@@ -132,6 +132,48 @@
Boolean getProductEnabled(@PathVariable("productidentifier") final String productIdentifier);
@RequestMapping(
+ value = "/products/{productidentifier}/balancesegmentsets/",
+ method = RequestMethod.POST,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.CONFLICT, exception = ProductInUseException.class)
+ void createBalanceSegmentSet(
+ @PathVariable("productidentifier") final String productIdentifier,
+ final BalanceSegmentSet balanceSegmentSet);
+
+ @RequestMapping(
+ value = "/products/{productidentifier}/balancesegmentsets/{balancesegmentsetidentifier}",
+ method = RequestMethod.GET,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE)
+ BalanceSegmentSet getBalanceSegmentSet(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("balancesegmentsetidentifier") final String balanceSegmentSetIdentifier);
+
+ @RequestMapping(
+ value = "/products/{productidentifier}/balancesegmentsets/{balancesegmentsetidentifier}",
+ method = RequestMethod.PUT,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE)
+ @ThrowsException(status = HttpStatus.CONFLICT, exception = ProductInUseException.class)
+ void changeBalanceSegmentSet(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("balancesegmentsetidentifier") final String balanceSegmentSetIdentifier,
+ BalanceSegmentSet balanceSegmentSet);
+
+ @RequestMapping(
+ value = "/products/{productidentifier}/balancesegmentsets/{balancesegmentsetidentifier}",
+ method = RequestMethod.DELETE,
+ produces = MediaType.ALL_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ @ThrowsException(status = HttpStatus.CONFLICT, exception = ProductInUseException.class)
+ void deleteBalanceSegmentSet(
+ @PathVariable("productidentifier") final String productIdentifier,
+ @PathVariable("balancesegmentsetidentifier") final String balanceSegmentSetIdentifier);
+
+ @RequestMapping(
value = "/products/{productidentifier}/tasks/",
method = RequestMethod.GET,
produces = MediaType.ALL_VALUE,
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSet.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSet.java
new file mode 100644
index 0000000..7d4c3da
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSet.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.api.v1.domain;
+
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+import io.mifos.core.lang.validation.constraints.ValidIdentifiers;
+import io.mifos.portfolio.api.v1.validation.ValidSegmentList;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Objects;
+import java.util.SortedSet;
+
+/**
+ * @author Myrle Krantz
+ */
+@ValidSegmentList
+public class BalanceSegmentSet {
+ @ValidIdentifier
+ private String identifier;
+
+ private SortedSet<BigDecimal> segments;
+
+ @ValidIdentifiers
+ private List<String> segmentIdentifiers;
+
+ public BalanceSegmentSet() {
+ }
+
+ public BalanceSegmentSet(String identifier, SortedSet<BigDecimal> segments, List<String> segmentIdentifiers) {
+ this.identifier = identifier;
+ this.segments = segments;
+ this.segmentIdentifiers = segmentIdentifiers;
+ }
+
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ public void setIdentifier(String identifier) {
+ this.identifier = identifier;
+ }
+
+ public SortedSet<BigDecimal> getSegments() {
+ return segments;
+ }
+
+ public void setSegments(SortedSet<BigDecimal> segments) {
+ this.segments = segments;
+ }
+
+ public List<String> getSegmentIdentifiers() {
+ return segmentIdentifiers;
+ }
+
+ public void setSegmentIdentifiers(List<String> segmentNames) {
+ this.segmentIdentifiers = segmentNames;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BalanceSegmentSet that = (BalanceSegmentSet) o;
+ return Objects.equals(identifier, that.identifier) &&
+ Objects.equals(segments, that.segments) &&
+ Objects.equals(segmentIdentifiers, that.segmentIdentifiers);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(identifier, segments, segmentIdentifiers);
+ }
+
+ @Override
+ public String toString() {
+ return "BalanceSegmentSet{" +
+ "identifier='" + identifier + '\'' +
+ ", segments=" + segments +
+ ", segmentIdentifiers=" + segmentIdentifiers +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
index b7170d0..efa69f8 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/ChargeDefinition.java
@@ -15,26 +15,25 @@
*/
package io.mifos.portfolio.api.v1.domain;
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+import io.mifos.core.lang.validation.constraints.ValidIdentifiers;
+import io.mifos.portfolio.api.v1.validation.ValidChargeDefinition;
import io.mifos.portfolio.api.v1.validation.ValidChargeReference;
import io.mifos.portfolio.api.v1.validation.ValidPaymentCycleUnit;
-import io.mifos.core.lang.validation.constraints.ValidIdentifier;
import org.hibernate.validator.constraints.NotBlank;
-import org.hibernate.validator.constraints.ScriptAssert;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.temporal.ChronoUnit;
+import java.util.List;
import java.util.Objects;
/**
* @author Myrle Krantz
*/
@SuppressWarnings({"unused", "WeakerAccess"})
-@ScriptAssert(lang = "javascript", script = "_this.amount !== null " +
- "&& _this.amount.scale() <= 4 " +
- "&& ((_this.accrueAction === null && _this.accrualAccountDesignator === null) || (_this.accrueAction !== null && _this.accrualAccountDesignator !== null))" +
- "&& ((_this.chargeMethod == 'PROPORTIONAL' && _this.proportionalTo !== null) || (_this.chargeMethod == 'FIXED' && _this.proportionalTo === null))")
+@ValidChargeDefinition
public class ChargeDefinition {
@SuppressWarnings("WeakerAccess")
public enum ChargeMethod {
@@ -82,6 +81,12 @@
private boolean readOnly;
+ @ValidIdentifier(optional = true)
+ private String forSegmentSet;
+
+ @ValidIdentifiers(optional = true)
+ private List<String> forSegments;
+
public ChargeDefinition() {
}
@@ -190,6 +195,22 @@
this.readOnly = readOnly;
}
+ public String getForSegmentSet() {
+ return forSegmentSet;
+ }
+
+ public void setForSegmentSet(String forSegmentSet) {
+ this.forSegmentSet = forSegmentSet;
+ }
+
+ public List<String> getForSegments() {
+ return forSegments;
+ }
+
+ public void setForSegments(List<String> forSegments) {
+ this.forSegments = forSegments;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -207,12 +228,14 @@
Objects.equals(fromAccountDesignator, that.fromAccountDesignator) &&
Objects.equals(accrualAccountDesignator, that.accrualAccountDesignator) &&
Objects.equals(toAccountDesignator, that.toAccountDesignator) &&
- forCycleSizeUnit == that.forCycleSizeUnit;
+ forCycleSizeUnit == that.forCycleSizeUnit &&
+ Objects.equals(forSegmentSet, that.forSegmentSet) &&
+ Objects.equals(forSegments, that.forSegments);
}
@Override
public int hashCode() {
- return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, proportionalTo, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit, readOnly);
+ return Objects.hash(identifier, name, description, accrueAction, chargeAction, amount, chargeMethod, proportionalTo, fromAccountDesignator, accrualAccountDesignator, toAccountDesignator, forCycleSizeUnit, readOnly, forSegmentSet, forSegments);
}
@Override
@@ -231,6 +254,8 @@
", toAccountDesignator='" + toAccountDesignator + '\'' +
", forCycleSizeUnit=" + forCycleSizeUnit +
", readOnly=" + readOnly +
+ ", forSegmentSet='" + forSegmentSet + '\'' +
+ ", forSegments=" + forSegments +
'}';
}
}
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidChargeDefinition.java b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidChargeDefinition.java
new file mode 100644
index 0000000..4735798
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidChargeDefinition.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.api.v1.validation;
+
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CheckValidChargeDefinition implements ConstraintValidator<ValidChargeDefinition, ChargeDefinition> {
+ @Override
+ public void initialize(ValidChargeDefinition constraintAnnotation) {
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean isValid(ChargeDefinition value, ConstraintValidatorContext context) {
+ if (value.getAmount() == null)
+ return false;
+ if (value.getAmount().scale() > 4)
+ return false;
+ if (value.getAccrueAction() != null && value.getAccrualAccountDesignator() == null)
+ return false;
+ if (value.getAccrueAction() == null && value.getAccrualAccountDesignator() != null)
+ return false;
+ if (value.getChargeMethod() == ChargeDefinition.ChargeMethod.PROPORTIONAL &&
+ value.getProportionalTo() == null)
+ return false;
+ if (value.getChargeMethod() == ChargeDefinition.ChargeMethod.FIXED &&
+ value.getProportionalTo() != null)
+ return false;
+ if (value.getForSegmentSet() == null &&
+ value.getForSegments() != null)
+ return false;
+ if (value.getForSegmentSet() != null &&
+ value.getForSegments() == null)
+ return false;
+ if (value.getForSegmentSet() != null &&
+ value.getForSegments() != null &&
+ value.getForSegments().size() == 0)
+ return false;
+
+ return true;
+ }
+}
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidSegmentList.java b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidSegmentList.java
new file mode 100644
index 0000000..aeb02ec
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidSegmentList.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.api.v1.validation;
+
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.math.BigDecimal;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CheckValidSegmentList implements ConstraintValidator<ValidSegmentList, BalanceSegmentSet> {
+ @Override
+ public void initialize(ValidSegmentList constraintAnnotation) {
+
+ }
+
+ @Override
+ public boolean isValid(BalanceSegmentSet value, ConstraintValidatorContext context) {
+ if (value.getSegments() == null)
+ return false;
+ if (value.getSegmentIdentifiers() == null)
+ return false;
+
+ if (value.getSegments().size() + 1 != value.getSegmentIdentifiers().size())
+ return false;
+
+ for (final BigDecimal segment : value.getSegments()) {
+ if (segment.compareTo(BigDecimal.ZERO) <= 0)
+ return false;
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/validation/ValidChargeDefinition.java b/api/src/main/java/io/mifos/portfolio/api/v1/validation/ValidChargeDefinition.java
new file mode 100644
index 0000000..680fe28
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/validation/ValidChargeDefinition.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.api.v1.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(
+ validatedBy = {CheckValidChargeDefinition.class}
+)
+public @interface ValidChargeDefinition {
+ String message() default "Invalid charge definition.";
+
+ Class<?>[] groups() default {};
+
+ Class<? extends Payload>[] payload() default {};
+}
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/validation/ValidSegmentList.java b/api/src/main/java/io/mifos/portfolio/api/v1/validation/ValidSegmentList.java
new file mode 100644
index 0000000..15269ec
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/validation/ValidSegmentList.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.api.v1.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(
+ validatedBy = {CheckValidSegmentList.class}
+)
+public @interface ValidSegmentList {
+ String message() default "Segments should be greater than 0 and should number the same as the identifiers.";
+
+ Class<?>[] groups() default {};
+
+ Class<? extends Payload>[] payload() default {};
+}
diff --git a/api/src/test/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSetTest.java b/api/src/test/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSetTest.java
new file mode 100644
index 0000000..c125654
--- /dev/null
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSetTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.portfolio.api.v1.domain;
+
+import io.mifos.core.test.domain.ValidationTest;
+import io.mifos.core.test.domain.ValidationTestCase;
+import org.junit.runners.Parameterized;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+/**
+ * @author Myrle Krantz
+ */
+public class BalanceSegmentSetTest extends ValidationTest<BalanceSegmentSet> {
+ public BalanceSegmentSetTest(ValidationTestCase<BalanceSegmentSet> testCase) {
+ super(testCase);
+ }
+
+ @Override
+ protected BalanceSegmentSet createValidTestSubject() {
+ final BalanceSegmentSet ret = new BalanceSegmentSet();
+ ret.setIdentifier("valid");
+ ret.setSegments(new TreeSet<>(Arrays.asList(BigDecimal.valueOf(100), BigDecimal.valueOf(10_000))));
+ ret.setSegmentIdentifiers(Arrays.asList("small", "medium", "large"));
+ return ret;
+ }
+
+ @Parameterized.Parameters
+ public static Collection testCases() {
+ final Collection<ValidationTestCase> ret = new ArrayList<>();
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("basicCase")
+ .adjustment(x -> {})
+ .valid(true));
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("null segments")
+ .adjustment(x -> x.setSegments(null))
+ .valid(false));
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("null identifiers")
+ .adjustment(x -> x.setSegmentIdentifiers(null))
+ .valid(false));
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("too short identifier list")
+ .adjustment(x -> x.setSegmentIdentifiers(Arrays.asList("small", "large")))
+ .valid(false));
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("negative segmentation")
+ .adjustment(x -> x.setSegments(new TreeSet<>(Arrays.asList(BigDecimal.ONE.negate(), BigDecimal.valueOf(100)))))
+ .valid(false));
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("invalid identifier")
+ .adjustment(x -> x.setIdentifier("//"))
+ .valid(false));
+ ret.add(new ValidationTestCase<BalanceSegmentSet>("invalid segment identifier")
+ .adjustment(x -> x.setSegmentIdentifiers(Arrays.asList("small", "large", "//")))
+ .valid(false));
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/api/src/test/java/io/mifos/portfolio/api/v1/domain/ChargeDefinitionTest.java b/api/src/test/java/io/mifos/portfolio/api/v1/domain/ChargeDefinitionTest.java
index 750be0b..e8df923 100644
--- a/api/src/test/java/io/mifos/portfolio/api/v1/domain/ChargeDefinitionTest.java
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/ChargeDefinitionTest.java
@@ -24,7 +24,9 @@
import java.math.BigDecimal;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
/**
* @author Myrle Krantz
@@ -109,6 +111,24 @@
ret.add(new ValidationTestCase<ChargeDefinition>("proportionalToMaximumBalance")
.adjustment(x -> x.setProportionalTo("{maximumbalance}"))
.valid(true));
+ ret.add(new ValidationTestCase<ChargeDefinition>("segment set set but not list")
+ .adjustment(x -> x.setForSegmentSet("xyz"))
+ .valid(false));
+ ret.add(new ValidationTestCase<ChargeDefinition>("segment list set but not set")
+ .adjustment(x -> x.setForSegments(Arrays.asList("mno", "xyz")))
+ .valid(false));
+ ret.add(new ValidationTestCase<ChargeDefinition>("empty segment list")
+ .adjustment(x -> { x.setForSegmentSet("xyz"); x.setForSegments(Collections.emptyList());})
+ .valid(false));
+ ret.add(new ValidationTestCase<ChargeDefinition>("valid segment references")
+ .adjustment(x -> { x.setForSegmentSet("xyz"); x.setForSegments(Collections.singletonList("abc"));})
+ .valid(true));
+ ret.add(new ValidationTestCase<ChargeDefinition>("invalid segment set identifier")
+ .adjustment(x -> { x.setForSegmentSet("//"); x.setForSegments(Collections.singletonList("abc"));})
+ .valid(false));
+ ret.add(new ValidationTestCase<ChargeDefinition>("invalid segment list set identifier")
+ .adjustment(x -> { x.setForSegmentSet("xyz"); x.setForSegments(Collections.singletonList("//"));})
+ .valid(false));
return ret;
}
}