Merge pull request #5 from myrlen/develop

charge definitions which reference balance ranges.
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..a2daf1c
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSet.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+/**
+ * @author Myrle Krantz
+ */
+@ValidSegmentList
+public class BalanceSegmentSet {
+  @ValidIdentifier
+  private String identifier;
+
+  private List<BigDecimal> segments;
+
+  @ValidIdentifiers
+  private List<String> segmentIdentifiers;
+
+  public BalanceSegmentSet() {
+  }
+
+  public String getIdentifier() {
+    return identifier;
+  }
+
+  public void setIdentifier(String identifier) {
+    this.identifier = identifier;
+  }
+
+  public List<BigDecimal> getSegments() {
+    return segments;
+  }
+
+  public void setSegments(List<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..3890867 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,11 +15,11 @@
  */
 package io.mifos.portfolio.api.v1.domain;
 
+import io.mifos.core.lang.validation.constraints.ValidIdentifier;
+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;
@@ -31,10 +31,7 @@
  * @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 +79,15 @@
 
   private boolean readOnly;
 
+  @ValidIdentifier(optional = true)
+  private String forSegmentSet;
+
+  @ValidIdentifier(optional = true)
+  private String fromSegment;
+
+  @ValidIdentifier(optional = true)
+  private String toSegment;
+
   public ChargeDefinition() {
   }
 
@@ -190,6 +196,30 @@
     this.readOnly = readOnly;
   }
 
+  public String getForSegmentSet() {
+    return forSegmentSet;
+  }
+
+  public void setForSegmentSet(String forSegmentSet) {
+    this.forSegmentSet = forSegmentSet;
+  }
+
+  public String getFromSegment() {
+    return fromSegment;
+  }
+
+  public void setFromSegment(String fromSegment) {
+    this.fromSegment = fromSegment;
+  }
+
+  public String getToSegment() {
+    return toSegment;
+  }
+
+  public void setToSegment(String toSegment) {
+    this.toSegment = toSegment;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
@@ -207,12 +237,15 @@
         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(fromSegment, that.fromSegment) &&
+        Objects.equals(toSegment, that.toSegment);
   }
 
   @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, fromSegment, toSegment);
   }
 
   @Override
@@ -231,6 +264,9 @@
         ", toAccountDesignator='" + toAccountDesignator + '\'' +
         ", forCycleSizeUnit=" + forCycleSizeUnit +
         ", readOnly=" + readOnly +
+        ", forSegmentSet='" + forSegmentSet + '\'' +
+        ", fromSegment='" + fromSegment + '\'' +
+        ", toSegment='" + toSegment + '\'' +
         '}';
   }
 }
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/events/BalanceSegmentSetEvent.java b/api/src/main/java/io/mifos/portfolio/api/v1/events/BalanceSegmentSetEvent.java
new file mode 100644
index 0000000..8cbaf75
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/events/BalanceSegmentSetEvent.java
@@ -0,0 +1,72 @@
+/*
+ * 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.events;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+public class BalanceSegmentSetEvent {
+  private String productIdentifier;
+  private String balanceSegmentSetIdentifier;
+
+  public BalanceSegmentSetEvent() {
+  }
+
+  public BalanceSegmentSetEvent(String productIdentifier, String balanceSegmentSetIdentifier) {
+    this.productIdentifier = productIdentifier;
+    this.balanceSegmentSetIdentifier = balanceSegmentSetIdentifier;
+  }
+
+  public String getProductIdentifier() {
+    return productIdentifier;
+  }
+
+  public void setProductIdentifier(String productIdentifier) {
+    this.productIdentifier = productIdentifier;
+  }
+
+  public String getBalanceSegmentSetIdentifier() {
+    return balanceSegmentSetIdentifier;
+  }
+
+  public void setBalanceSegmentSetIdentifier(String balanceSegmentSetIdentifier) {
+    this.balanceSegmentSetIdentifier = balanceSegmentSetIdentifier;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    BalanceSegmentSetEvent that = (BalanceSegmentSetEvent) o;
+    return Objects.equals(productIdentifier, that.productIdentifier) &&
+        Objects.equals(balanceSegmentSetIdentifier, that.balanceSegmentSetIdentifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(productIdentifier, balanceSegmentSetIdentifier);
+  }
+
+  @Override
+  public String toString() {
+    return "BalanceSegmentSetEvent{" +
+        "productIdentifier='" + productIdentifier + '\'' +
+        ", balanceSegmentSetIdentifier='" + balanceSegmentSetIdentifier + '\'' +
+        '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java b/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java
index 9412d57..4db9c5f 100644
--- a/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/events/EventConstants.java
@@ -29,6 +29,9 @@
   String PUT_PRODUCT_ENABLE = "put-enable";
   String POST_CASE = "post-case";
   String PUT_CASE = "put-case";
+  String POST_BALANCE_SEGMENT_SET = "post-balance-segment-set";
+  String PUT_BALANCE_SEGMENT_SET = "put-balance-segment-set";
+  String DELETE_BALANCE_SEGMENT_SET = "delete-balance-segment-set";
   String POST_TASK_DEFINITION = "post-task-definition";
   String PUT_TASK_DEFINITION = "put-task-definition";
   String DELETE_TASK_DEFINITION = "delete-task-definition";
@@ -44,6 +47,9 @@
   String SELECTOR_PUT_PRODUCT_ENABLE = SELECTOR_NAME + " = '" + PUT_PRODUCT_ENABLE + "'";
   String SELECTOR_POST_CASE = SELECTOR_NAME + " = '" + POST_CASE + "'";
   String SELECTOR_PUT_CASE = SELECTOR_NAME + " = '" + PUT_CASE + "'";
+  String SELECTOR_POST_BALANCE_SEGMENT_SET = SELECTOR_NAME + " = '" + POST_BALANCE_SEGMENT_SET + "'";
+  String SELECTOR_PUT_BALANCE_SEGMENT_SET = SELECTOR_NAME + " = '" + PUT_BALANCE_SEGMENT_SET + "'";
+  String SELECTOR_DELETE_BALANCE_SEGMENT_SET = SELECTOR_NAME + " = '" + DELETE_BALANCE_SEGMENT_SET + "'";
   String SELECTOR_POST_TASK_DEFINITION = SELECTOR_NAME + " = '" + POST_TASK_DEFINITION + "'";
   String SELECTOR_PUT_TASK_DEFINITION = SELECTOR_NAME + " = '" + PUT_TASK_DEFINITION + "'";
   String SELECTOR_DELETE_TASK_DEFINITION = SELECTOR_NAME + " = '" + DELETE_TASK_DEFINITION + "'";
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..fcdf02a
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidChargeDefinition.java
@@ -0,0 +1,58 @@
+/*
+ * 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 &&
+        value.getForSegmentSet() == null) //Even if the charge is a fixed charge, we need a proportional to for segment sets.
+       return false;
+    if (value.getForSegmentSet() == null &&
+        (value.getFromSegment() != null || value.getToSegment() != null))
+      return false;
+    if (value.getForSegmentSet() != null &&
+        (value.getFromSegment() == null || value.getToSegment() == null))
+      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..96fc98d
--- /dev/null
+++ b/api/src/main/java/io/mifos/portfolio/api/v1/validation/CheckValidSegmentList.java
@@ -0,0 +1,59 @@
+/*
+ * 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() == 0)
+      return false;
+
+    if (value.getSegments().size() != value.getSegmentIdentifiers().size())
+      return false;
+
+    if (value.getSegments().get(0).compareTo(BigDecimal.ZERO) != 0)
+      return false;
+
+    for (int i = 0; i < value.getSegments().size() -1; i++) {
+      final BigDecimal segment1 = value.getSegments().get(i);
+      final BigDecimal segment2 = value.getSegments().get(i+1);
+
+      if (segment1.compareTo(segment2) > 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..d787a96
--- /dev/null
+++ b/api/src/test/java/io/mifos/portfolio/api/v1/domain/BalanceSegmentSetTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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(Arrays.asList(BigDecimal.ZERO, 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>("too short segment list")
+        .adjustment(x -> x.setSegments(Arrays.asList(BigDecimal.ZERO, BigDecimal.valueOf(100))))
+        .valid(false));
+    ret.add(new ValidationTestCase<BalanceSegmentSet>("non-zero first entry")
+        .adjustment(x -> x.setSegments(Arrays.asList(BigDecimal.ONE, BigDecimal.valueOf(100), BigDecimal.valueOf(10_000))))
+        .valid(false));
+    ret.add(new ValidationTestCase<BalanceSegmentSet>("mis-ordered segmentation")
+        .adjustment(x -> x.setSegments(Arrays.asList(BigDecimal.ZERO, BigDecimal.valueOf(10_000), 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..8f9ad7e 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
@@ -98,17 +98,47 @@
             .adjustment(x -> x.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED))
             .valid(false));
     ret.add(new ValidationTestCase<ChargeDefinition>("missingProportionalToIdentifierOnFixedCharge")
-            .adjustment(x -> {
-              x.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
-              x.setProportionalTo(null);
-            })
-            .valid(true));
+        .adjustment(x -> {
+          x.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+          x.setProportionalTo(null);
+        })
+        .valid(true));
+    ret.add(new ValidationTestCase<ChargeDefinition>("fixed charge proportional so that segment set can be set.")
+        .adjustment(x -> {
+          x.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+          x.setProportionalTo("something");
+          x.setForSegmentSet("xyz");
+          x.setFromSegment("abc");
+          x.setToSegment("def");
+        })
+        .valid(true));
     ret.add(new ValidationTestCase<ChargeDefinition>("proportionalToRunningBalance")
         .adjustment(x -> x.setProportionalTo("{runningbalance}"))
         .valid(true));
     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>("from segment set but not set")
+        .adjustment(x -> x.setFromSegment("abc"))
+        .valid(false));
+    ret.add(new ValidationTestCase<ChargeDefinition>("to segment set but not set")
+        .adjustment(x -> x.setFromSegment("def"))
+        .valid(false));
+    ret.add(new ValidationTestCase<ChargeDefinition>("valid segment references")
+        .adjustment(x -> { x.setForSegmentSet("xyz"); x.setFromSegment("abc"); x.setToSegment("def");})
+        .valid(true));
+    ret.add(new ValidationTestCase<ChargeDefinition>("invalid segment set identifier")
+        .adjustment(x -> { x.setForSegmentSet("//"); x.setFromSegment("abc"); x.setToSegment("def");})
+        .valid(false));
+    ret.add(new ValidationTestCase<ChargeDefinition>("invalid from segment identifier")
+        .adjustment(x -> { x.setForSegmentSet("xyz"); x.setFromSegment("//"); x.setToSegment("def");})
+        .valid(false));
+    ret.add(new ValidationTestCase<ChargeDefinition>("invalid to segment identifier")
+        .adjustment(x -> { x.setForSegmentSet("xyz"); x.setFromSegment("abc"); x.setToSegment("//");})
+        .valid(false));
     return ret;
   }
 }
diff --git a/component-test/src/main/java/io/mifos/portfolio/Fixture.java b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
index f533a12..dc3cdfe 100644
--- a/component-test/src/main/java/io/mifos/portfolio/Fixture.java
+++ b/component-test/src/main/java/io/mifos/portfolio/Fixture.java
@@ -52,7 +52,7 @@
     product.setName("Agricultural Loan");
     product.setDescription("Loan for seeds or agricultural equipment");
     product.setTermRange(new TermRange(ChronoUnit.MONTHS, 12));
-    product.setBalanceRange(new BalanceRange(fixScale(BigDecimal.ZERO), fixScale(new BigDecimal(10000))));
+    product.setBalanceRange(new BalanceRange(fixScale(BigDecimal.ZERO), fixScale(new BigDecimal(10_000))));
     product.setInterestRange(new InterestRange(BigDecimal.valueOf(3_00, 2), BigDecimal.valueOf(12_00, 2)));
     product.setInterestBasis(InterestBasis.CURRENT_BALANCE);
 
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
index 4ab454a..c4a9e5f 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestAccountingInteractionInLoanWorkflow.java
@@ -24,13 +24,13 @@
 import io.mifos.individuallending.api.v1.domain.caseinstance.CaseParameters;
 import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
 import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.api.v1.events.IndividualLoanCommandEvent;
 import io.mifos.individuallending.api.v1.events.IndividualLoanEventConstants;
-import io.mifos.portfolio.api.v1.domain.Case;
-import io.mifos.portfolio.api.v1.domain.CostComponent;
-import io.mifos.portfolio.api.v1.domain.Product;
-import io.mifos.portfolio.api.v1.domain.TaskDefinition;
+import io.mifos.portfolio.api.v1.domain.*;
+import io.mifos.portfolio.api.v1.events.BalanceSegmentSetEvent;
+import io.mifos.portfolio.api.v1.events.ChargeDefinitionEvent;
 import io.mifos.portfolio.api.v1.events.EventConstants;
 import io.mifos.rhythm.spi.v1.client.BeatListener;
 import io.mifos.rhythm.spi.v1.domain.BeatPublish;
@@ -54,9 +54,14 @@
  * @author Myrle Krantz
  */
 public class TestAccountingInteractionInLoanWorkflow extends AbstractPortfolioTest {
-  private static final BigDecimal PROCESSING_FEE_AMOUNT = BigDecimal.valueOf(10_0000, MINOR_CURRENCY_UNIT_DIGITS);
-  private static final BigDecimal LOAN_ORIGINATION_FEE_AMOUNT = BigDecimal.valueOf(100_0000, MINOR_CURRENCY_UNIT_DIGITS);
-  private static final BigDecimal DISBURSEMENT_FEE_AMOUNT = BigDecimal.valueOf(1_0000, MINOR_CURRENCY_UNIT_DIGITS);
+  private static final BigDecimal PROCESSING_FEE_AMOUNT = BigDecimal.valueOf(100_00, MINOR_CURRENCY_UNIT_DIGITS);
+  private static final BigDecimal LOAN_ORIGINATION_FEE_AMOUNT = BigDecimal.valueOf(100_00, MINOR_CURRENCY_UNIT_DIGITS);
+  private static final BigDecimal DISBURSEMENT_FEE_LOWER_RANGE_AMOUNT = BigDecimal.valueOf(10_00, MINOR_CURRENCY_UNIT_DIGITS);
+  private static final BigDecimal DISBURSEMENT_FEE_UPPER_RANGE_AMOUNT = BigDecimal.valueOf(1_00, MINOR_CURRENCY_UNIT_DIGITS);
+  private static final String DISBURSEMENT_RANGES = "disbursement_ranges";
+  private static final String DISBURSEMENT_LOWER_RANGE = "smaller";
+  private static final String DISBURSEMENT_UPPER_RANGE = "larger";
+  private static final String UPPER_RANGE_DISBURSEMENT_FEE_ID = ChargeIdentifiers.DISBURSEMENT_FEE_ID + "2";
 
   private BeatListener portfolioBeatListener;
 
@@ -90,7 +95,26 @@
     step2CreateCase();
     step3OpenCase();
     step4ApproveCase();
-    step5Disburse(BigDecimal.valueOf(2000L).setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
+    step5Disburse(
+        BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
+        UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
+    step6CalculateInterestAccrual();
+    step7PaybackPartialAmount(expectedCurrentBalance);
+    step8Close();
+  }
+
+  @Test
+  public void workflowWithTwoUnequalDisbursals() throws InterruptedException {
+    step1CreateProduct();
+    step2CreateCase();
+    step3OpenCase();
+    step4ApproveCase();
+    step5Disburse(
+        BigDecimal.valueOf(500_00, MINOR_CURRENCY_UNIT_DIGITS),
+        ChargeIdentifiers.DISBURSEMENT_FEE_ID, BigDecimal.valueOf(10_00, MINOR_CURRENCY_UNIT_DIGITS));
+    step5Disburse(
+        BigDecimal.valueOf(1_500_00, MINOR_CURRENCY_UNIT_DIGITS),
+        UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(15_00, MINOR_CURRENCY_UNIT_DIGITS));
     step6CalculateInterestAccrual();
     step7PaybackPartialAmount(expectedCurrentBalance);
     step8Close();
@@ -102,7 +126,9 @@
     step2CreateCase();
     step3OpenCase();
     step4ApproveCase();
-    step5Disburse(BigDecimal.valueOf(2000L).setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
+    step5Disburse(
+        BigDecimal.valueOf(2_000_00, MINOR_CURRENCY_UNIT_DIGITS),
+        UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.valueOf(20_00, MINOR_CURRENCY_UNIT_DIGITS));
     step6CalculateInterestAccrual();
     final BigDecimal repayment1 = expectedCurrentBalance.divide(BigDecimal.valueOf(2), BigDecimal.ROUND_HALF_EVEN);
     step7PaybackPartialAmount(repayment1.setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
@@ -110,13 +136,18 @@
     step8Close();
   }
 
-  @Test(expected = IllegalArgumentException.class)
+  @Test
   public void workflowWithNegativePaymentSize() throws InterruptedException {
     step1CreateProduct();
     step2CreateCase();
     step3OpenCase();
     step4ApproveCase();
-    step5Disburse(BigDecimal.valueOf(-2).setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
+    try {
+      step5Disburse(BigDecimal.valueOf(-2).setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN),
+          UPPER_RANGE_DISBURSEMENT_FEE_ID, BigDecimal.ZERO.setScale(MINOR_CURRENCY_UNIT_DIGITS, BigDecimal.ROUND_HALF_EVEN));
+      Assert.fail("Expected an IllegalArgumentException.");
+    }
+    catch (IllegalArgumentException ignored) { }
   }
 
   //Create product and set charges to fixed fees.
@@ -124,9 +155,48 @@
     logger.info("step1CreateProduct");
     product = createProduct();
 
+    final BalanceSegmentSet balanceSegmentSet = new BalanceSegmentSet();
+    balanceSegmentSet.setIdentifier(DISBURSEMENT_RANGES);
+    balanceSegmentSet.setSegmentIdentifiers(Arrays.asList(DISBURSEMENT_LOWER_RANGE, DISBURSEMENT_UPPER_RANGE));
+    balanceSegmentSet.setSegments(Arrays.asList(BigDecimal.ZERO, BigDecimal.valueOf(1_000_0000, 4)));
+    portfolioManager.createBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet);
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BALANCE_SEGMENT_SET, new BalanceSegmentSetEvent(product.getIdentifier(), balanceSegmentSet.getIdentifier())));
+
     setFeeToFixedValue(product.getIdentifier(), ChargeIdentifiers.PROCESSING_FEE_ID, PROCESSING_FEE_AMOUNT);
     setFeeToFixedValue(product.getIdentifier(), ChargeIdentifiers.LOAN_ORIGINATION_FEE_ID, LOAN_ORIGINATION_FEE_AMOUNT);
-    setFeeToFixedValue(product.getIdentifier(), ChargeIdentifiers.DISBURSEMENT_FEE_ID, DISBURSEMENT_FEE_AMOUNT);
+
+    final ChargeDefinition lowerRangeDisbursementFeeChargeDefinition
+        = portfolioManager.getChargeDefinition(product.getIdentifier(), ChargeIdentifiers.DISBURSEMENT_FEE_ID);
+    lowerRangeDisbursementFeeChargeDefinition.setChargeMethod(ChargeDefinition.ChargeMethod.FIXED);
+    lowerRangeDisbursementFeeChargeDefinition.setAmount(DISBURSEMENT_FEE_LOWER_RANGE_AMOUNT);
+    lowerRangeDisbursementFeeChargeDefinition.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
+    lowerRangeDisbursementFeeChargeDefinition.setForSegmentSet(DISBURSEMENT_RANGES);
+    lowerRangeDisbursementFeeChargeDefinition.setFromSegment(DISBURSEMENT_LOWER_RANGE);
+    lowerRangeDisbursementFeeChargeDefinition.setToSegment(DISBURSEMENT_LOWER_RANGE);
+
+    portfolioManager.changeChargeDefinition(product.getIdentifier(), ChargeIdentifiers.DISBURSEMENT_FEE_ID, lowerRangeDisbursementFeeChargeDefinition);
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CHARGE_DEFINITION,
+        new ChargeDefinitionEvent(product.getIdentifier(), ChargeIdentifiers.DISBURSEMENT_FEE_ID)));
+
+    final ChargeDefinition upperRangeDisbursementFeeChargeDefinition = new ChargeDefinition();
+    upperRangeDisbursementFeeChargeDefinition.setIdentifier(UPPER_RANGE_DISBURSEMENT_FEE_ID);
+    upperRangeDisbursementFeeChargeDefinition.setName(UPPER_RANGE_DISBURSEMENT_FEE_ID);
+    upperRangeDisbursementFeeChargeDefinition.setDescription(lowerRangeDisbursementFeeChargeDefinition.getDescription());
+    upperRangeDisbursementFeeChargeDefinition.setFromAccountDesignator(lowerRangeDisbursementFeeChargeDefinition.getFromAccountDesignator());
+    upperRangeDisbursementFeeChargeDefinition.setToAccountDesignator(lowerRangeDisbursementFeeChargeDefinition.getToAccountDesignator());
+    upperRangeDisbursementFeeChargeDefinition.setAccrualAccountDesignator(lowerRangeDisbursementFeeChargeDefinition.getAccrualAccountDesignator());
+    upperRangeDisbursementFeeChargeDefinition.setAccrueAction(lowerRangeDisbursementFeeChargeDefinition.getAccrueAction());
+    upperRangeDisbursementFeeChargeDefinition.setChargeAction(lowerRangeDisbursementFeeChargeDefinition.getChargeAction());
+    upperRangeDisbursementFeeChargeDefinition.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    upperRangeDisbursementFeeChargeDefinition.setAmount(DISBURSEMENT_FEE_UPPER_RANGE_AMOUNT);
+    upperRangeDisbursementFeeChargeDefinition.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR.getValue());
+    upperRangeDisbursementFeeChargeDefinition.setForSegmentSet(DISBURSEMENT_RANGES);
+    upperRangeDisbursementFeeChargeDefinition.setFromSegment(DISBURSEMENT_UPPER_RANGE);
+    upperRangeDisbursementFeeChargeDefinition.setToSegment(DISBURSEMENT_UPPER_RANGE);
+
+    portfolioManager.createChargeDefinition(product.getIdentifier(), upperRangeDisbursementFeeChargeDefinition);
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_CHARGE_DEFINITION,
+        new ChargeDefinitionEvent(product.getIdentifier(), UPPER_RANGE_DISBURSEMENT_FEE_ID)));
 
     taskDefinition = createTaskDefinition(product);
 
@@ -230,14 +300,17 @@
   }
 
   //Approve the case, accept a loan origination fee, and prepare to disburse the loan by earmarking the funds.
-  private void step5Disburse(final BigDecimal amount) throws InterruptedException {
+  private void step5Disburse(
+      final BigDecimal amount,
+      final String whichDisbursementFee,
+      final BigDecimal disbursementFeeAmount) throws InterruptedException {
     logger.info("step5Disburse");
     checkCostComponentForActionCorrect(
         product.getIdentifier(),
         customerCase.getIdentifier(),
         Action.DISBURSE,
         Collections.singleton(AccountDesignators.ENTRY),
-        amount, new CostComponent(ChargeIdentifiers.DISBURSEMENT_FEE_ID, DISBURSEMENT_FEE_AMOUNT),
+        amount, new CostComponent(whichDisbursementFee, disbursementFeeAmount),
         new CostComponent(ChargeIdentifiers.DISBURSE_PAYMENT_ID, amount));
     checkStateTransfer(
         product.getIdentifier(),
@@ -254,12 +327,12 @@
     final Set<Debtor> debtors = new HashSet<>();
     debtors.add(new Debtor(pendingDisbursalAccountIdentifier, amount.toPlainString()));
     debtors.add(new Debtor(AccountingFixture.LOANS_PAYABLE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
-    debtors.add(new Debtor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, DISBURSEMENT_FEE_AMOUNT.toPlainString()));
+    debtors.add(new Debtor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, disbursementFeeAmount.toPlainString()));
 
     final Set<Creditor> creditors = new HashSet<>();
     creditors.add(new Creditor(customerLoanAccountIdentifier, amount.toPlainString()));
     creditors.add(new Creditor(AccountingFixture.TELLER_ONE_ACCOUNT_IDENTIFIER, amount.toPlainString()));
-    creditors.add(new Creditor(AccountingFixture.DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER, DISBURSEMENT_FEE_AMOUNT.toPlainString()));
+    creditors.add(new Creditor(AccountingFixture.DISBURSEMENT_FEE_INCOME_ACCOUNT_IDENTIFIER, disbursementFeeAmount.toPlainString()));
     AccountingFixture.verifyTransfer(ledgerManager, debtors, creditors, product.getIdentifier(), customerCase.getIdentifier(), Action.DISBURSE);
 
     expectedCurrentBalance = expectedCurrentBalance.add(amount);
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestBalanceSegmentSets.java b/component-test/src/main/java/io/mifos/portfolio/TestBalanceSegmentSets.java
new file mode 100644
index 0000000..197b3d5
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/portfolio/TestBalanceSegmentSets.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import io.mifos.portfolio.api.v1.client.ProductInUseException;
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+import io.mifos.portfolio.api.v1.domain.Product;
+import io.mifos.portfolio.api.v1.events.BalanceSegmentSetEvent;
+import io.mifos.portfolio.api.v1.events.EventConstants;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+
+/**
+ * @author Myrle Krantz
+ */
+public class TestBalanceSegmentSets extends AbstractPortfolioTest {
+  @Test
+  public void testBalanceSegmentSetManagement() throws InterruptedException {
+    final Product product = createProduct();
+
+    final BalanceSegmentSet balanceSegmentSet = new BalanceSegmentSet();
+    balanceSegmentSet.setIdentifier(testEnvironment.generateUniqueIdentifer("bss"));
+    balanceSegmentSet.setSegments(Arrays.asList(
+        BigDecimal.ZERO.setScale(4, BigDecimal.ROUND_HALF_EVEN),
+        BigDecimal.TEN.setScale(4, BigDecimal.ROUND_HALF_EVEN),
+        BigDecimal.valueOf(10_000_0000, 4)));
+    balanceSegmentSet.setSegmentIdentifiers(Arrays.asList("abc", "def", "ghi"));
+
+    portfolioManager.createBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet);
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BALANCE_SEGMENT_SET, new BalanceSegmentSetEvent(product.getIdentifier(), balanceSegmentSet.getIdentifier())));
+
+    final BalanceSegmentSet createdBalanceSegmentSet = portfolioManager.getBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet.getIdentifier());
+    Assert.assertEquals(balanceSegmentSet, createdBalanceSegmentSet);
+
+    balanceSegmentSet.setSegments(Arrays.asList(
+        BigDecimal.ZERO.setScale(4, BigDecimal.ROUND_HALF_EVEN),
+        BigDecimal.valueOf(100_0000, 4),
+        BigDecimal.valueOf(10_000_0000, 4)));
+    balanceSegmentSet.setSegmentIdentifiers(Arrays.asList("abc", "def", "ghi"));
+
+    portfolioManager.changeBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet.getIdentifier(), balanceSegmentSet);
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_BALANCE_SEGMENT_SET, new BalanceSegmentSetEvent(product.getIdentifier(), balanceSegmentSet.getIdentifier())));
+
+    final BalanceSegmentSet changedBalanceSegmentSet = portfolioManager.getBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet.getIdentifier());
+    Assert.assertEquals(balanceSegmentSet, changedBalanceSegmentSet);
+
+    portfolioManager.deleteBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet.getIdentifier());
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.DELETE_BALANCE_SEGMENT_SET, new BalanceSegmentSetEvent(product.getIdentifier(), balanceSegmentSet.getIdentifier())));
+
+    enableProduct(product);
+
+    createCase(product.getIdentifier());
+
+    try {
+      portfolioManager.createBalanceSegmentSet(product.getIdentifier(), balanceSegmentSet);
+      Assert.fail("shouldn't be able to create a balance segment set in a product in use.");
+    }
+    catch (final ProductInUseException ignored) { }
+  }
+}
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestSuite.java b/component-test/src/main/java/io/mifos/portfolio/TestSuite.java
index e7bd386..44d8cc3 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestSuite.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestSuite.java
@@ -24,6 +24,7 @@
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
     TestAccountingInteractionInLoanWorkflow.class,
+    TestBalanceSegmentSets.class,
     TestCases.class,
     TestChargeDefinitions.class,
     TestCommands.class,
diff --git a/component-test/src/main/java/io/mifos/portfolio/listener/BalanceSegmentSetEventListener.java b/component-test/src/main/java/io/mifos/portfolio/listener/BalanceSegmentSetEventListener.java
new file mode 100644
index 0000000..528fc7c
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/portfolio/listener/BalanceSegmentSetEventListener.java
@@ -0,0 +1,70 @@
+/*
+ * 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.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.portfolio.api.v1.events.BalanceSegmentSetEvent;
+import io.mifos.portfolio.api.v1.events.EventConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+public class BalanceSegmentSetEventListener {
+  private final EventRecorder eventRecorder;
+
+  @SuppressWarnings("SpringJavaAutowiringInspection")
+  @Autowired
+  public BalanceSegmentSetEventListener(final EventRecorder eventRecorder) {
+    super();
+    this.eventRecorder = eventRecorder;
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_POST_BALANCE_SEGMENT_SET
+  )
+  public void onCreateBalanceSegmentSet(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                                        final String payload) {
+    this.eventRecorder.event(tenant, EventConstants.POST_BALANCE_SEGMENT_SET, payload, BalanceSegmentSetEvent.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_PUT_BALANCE_SEGMENT_SET
+  )
+  public void onChangeBalanceSegmentSet(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                                        final String payload) {
+    this.eventRecorder.event(tenant, EventConstants.PUT_BALANCE_SEGMENT_SET, payload, BalanceSegmentSetEvent.class);
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_DELETE_BALANCE_SEGMENT_SET
+  )
+  public void onDeleteBalanceSegmentSet(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+                                        final String payload) {
+    this.eventRecorder.event(tenant, EventConstants.DELETE_BALANCE_SEGMENT_SET, payload, BalanceSegmentSetEvent.class);
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ChargeRange.java b/service/src/main/java/io/mifos/individuallending/internal/service/ChargeRange.java
new file mode 100644
index 0000000..cd293d5
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ChargeRange.java
@@ -0,0 +1,64 @@
+/*
+ * 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.individuallending.internal.service;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+class ChargeRange {
+  final private BigDecimal from;
+  @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+  final private Optional<BigDecimal> to;
+
+  ChargeRange(
+      final BigDecimal from,
+      @SuppressWarnings("OptionalUsedAsFieldOrParameterType") final Optional<BigDecimal> to) {
+    this.from = from;
+    this.to = to;
+  }
+
+  boolean amountIsWithinRange(BigDecimal amountProportionalTo) {
+    return to.map(bigDecimal -> from.compareTo(amountProportionalTo) <= 0 &&
+        bigDecimal.compareTo(amountProportionalTo) > 0)
+        .orElseGet(() -> from.compareTo(amountProportionalTo) <= 0);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ChargeRange that = (ChargeRange) o;
+    return Objects.equals(from, that.from) &&
+        Objects.equals(to, that.to);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(from, to);
+  }
+
+  @Override
+  public String toString() {
+    return "ChargeRange{" +
+        "from=" + from +
+        ", to=" + to +
+        '}';
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
index 318db3b..c3ba820 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/CostComponentService.java
@@ -47,14 +47,14 @@
   private static final int EXTRA_PRECISION = 4;
   private static final int RUNNING_CALCULATION_PRECISION = 8;
 
-  private final IndividualLoanService individualLoanService;
+  private final ScheduledChargesService scheduledChargesService;
   private final AccountingAdapter accountingAdapter;
 
   @Autowired
   public CostComponentService(
-          final IndividualLoanService individualLoanService,
-          final AccountingAdapter accountingAdapter) {
-    this.individualLoanService = individualLoanService;
+      final ScheduledChargesService scheduledChargesService,
+      final AccountingAdapter accountingAdapter) {
+    this.scheduledChargesService = scheduledChargesService;
     this.accountingAdapter = accountingAdapter;
   }
 
@@ -93,7 +93,7 @@
     final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.OPEN, today()));
-    final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
         productIdentifier, scheduledActions);
 
     return getCostComponentsForScheduledCharges(
@@ -112,7 +112,7 @@
     final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.DENY, today()));
-    final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
         productIdentifier, scheduledActions);
 
     return getCostComponentsForScheduledCharges(
@@ -132,7 +132,7 @@
     final String productIdentifier = dataContextOfAction.getProduct().getIdentifier();
     final int minorCurrencyUnitDigits = dataContextOfAction.getProduct().getMinorCurrencyUnitDigits();
     final List<ScheduledAction> scheduledActions = Collections.singletonList(new ScheduledAction(Action.APPROVE, today()));
-    final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
         productIdentifier, scheduledActions);
 
     return getCostComponentsForScheduledCharges(
@@ -173,7 +173,7 @@
     else
       disbursalSize = requestedDisbursalSize.negate();
 
-    final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
         productIdentifier, scheduledActions);
 
 
@@ -219,7 +219,7 @@
     final LocalDate today = today();
     final ScheduledAction interestAction = new ScheduledAction(Action.APPLY_INTEREST, today, new Period(1, today));
 
-    final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
         productIdentifier,
         Collections.singletonList(interestAction));
 
@@ -271,7 +271,7 @@
       final List<ScheduledAction> hypotheticalScheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(
           today(),
           caseParameters);
-      final List<ScheduledCharge> hypotheticalScheduledCharges = individualLoanService.getScheduledCharges(
+      final List<ScheduledCharge> hypotheticalScheduledCharges = scheduledChargesService.getScheduledCharges(
           productIdentifier,
           hypotheticalScheduledActions);
       loanPaymentSize = getLoanPaymentSize(
@@ -281,7 +281,7 @@
           hypotheticalScheduledCharges);
     }
 
-    final List<ScheduledCharge> scheduledChargesForThisAction = individualLoanService.getScheduledCharges(
+    final List<ScheduledCharge> scheduledChargesForThisAction = scheduledChargesService.getScheduledCharges(
         productIdentifier,
         Collections.singletonList(scheduledAction));
 
@@ -307,12 +307,12 @@
         true);
   }
 
-  public static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+  private static boolean isAccruedChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
     return chargeDefinition.getAccrueAction() != null &&
         chargeDefinition.getChargeAction().equals(action.name());
   }
 
-  public static boolean isAccrualChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
+  private static boolean isAccrualChargeForAction(final ChargeDefinition chargeDefinition, final Action action) {
     return chargeDefinition.getAccrueAction() != null &&
         chargeDefinition.getAccrueAction().equals(action.name());
   }
@@ -367,7 +367,7 @@
     final LocalDate today = today();
     final ScheduledAction closeAction = new ScheduledAction(Action.CLOSE, today, new Period(1, today));
 
-    final List<ScheduledCharge> scheduledCharges = individualLoanService.getScheduledCharges(
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(
         productIdentifier,
         Collections.singletonList(closeAction));
 
@@ -440,6 +440,9 @@
             entryAccountAdjustment,
             balanceAdjustments);
         //TODO: getAmountProportionalTo is programmed under the assumption of non-accrual accounting.
+        if (scheduledCharge.getChargeRange().map(x ->
+            !x.amountIsWithinRange(amountProportionalTo)).orElse(false))
+          continue;
 
         final CostComponent costComponent = costComponentMap
             .computeIfAbsent(scheduledCharge.getChargeDefinition(), CostComponentService::constructEmptyCostComponent);
@@ -468,27 +471,37 @@
       final BigDecimal runningBalance,
       final BigDecimal loanPaymentSize,
       final Map<String, BigDecimal> balanceAdjustments) {
-    final Optional<ChargeProportionalDesignator> optionalChargeProportionalTo = proportionalToDesignator(scheduledCharge);
-    return optionalChargeProportionalTo.map(chargeProportionalTo -> {
-      switch (chargeProportionalTo) {
-        case NOT_PROPORTIONAL:
-          return BigDecimal.ZERO;
-        case MAXIMUM_BALANCE_DESIGNATOR:
-          return maximumBalance;
-        case RUNNING_BALANCE_DESIGNATOR:
-          return runningBalance.subtract(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO));
-        case REPAYMENT_DESIGNATOR:
-          return loanPaymentSize;
-        case PRINCIPAL_ADJUSTMENT_DESIGNATOR: {
-          final BigDecimal newRunningBalance
-              = runningBalance.subtract(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO));
-          final BigDecimal newLoanPaymentSize = loanPaymentSize.min(newRunningBalance);
-          return newLoanPaymentSize.add(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO)).abs();
-        }
-        default:
-          return BigDecimal.ZERO;
+    final Optional<ChargeProportionalDesignator> optionalChargeProportionalTo
+        = ChargeProportionalDesignator.fromString(scheduledCharge.getChargeDefinition().getProportionalTo());
+    return optionalChargeProportionalTo.map(chargeProportionalTo ->
+        getAmountProportionalTo(chargeProportionalTo, maximumBalance, runningBalance, loanPaymentSize, balanceAdjustments))
+        .orElse(BigDecimal.ZERO);
+  }
+
+  static BigDecimal getAmountProportionalTo(
+      final ChargeProportionalDesignator chargeProportionalTo,
+      final BigDecimal maximumBalance,
+      final BigDecimal runningBalance,
+      final BigDecimal loanPaymentSize,
+      final Map<String, BigDecimal> balanceAdjustments) {
+    switch (chargeProportionalTo) {
+      case NOT_PROPORTIONAL:
+        return BigDecimal.ZERO;
+      case MAXIMUM_BALANCE_DESIGNATOR:
+        return maximumBalance;
+      case RUNNING_BALANCE_DESIGNATOR:
+        return runningBalance.subtract(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO));
+      case REPAYMENT_DESIGNATOR:
+        return loanPaymentSize;
+      case PRINCIPAL_ADJUSTMENT_DESIGNATOR: {
+        final BigDecimal newRunningBalance
+            = runningBalance.subtract(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO));
+        final BigDecimal newLoanPaymentSize = loanPaymentSize.min(newRunningBalance);
+        return newLoanPaymentSize.add(balanceAdjustments.getOrDefault(AccountDesignators.CUSTOMER_LOAN, BigDecimal.ZERO)).abs();
       }
-    }).orElse(BigDecimal.ZERO);
+      default:
+        return BigDecimal.ZERO;
+    }
 //TODO: correctly implement charges which are proportional to other charges.
   }
 
@@ -499,14 +512,6 @@
     return ret;
   }
 
-  private static Optional<ChargeProportionalDesignator> proportionalToDesignator(final ScheduledCharge scheduledCharge) {
-    if (!scheduledCharge.getChargeDefinition().getChargeMethod().equals(ChargeDefinition.ChargeMethod.PROPORTIONAL) &&
-        !scheduledCharge.getChargeDefinition().getChargeMethod().equals(ChargeDefinition.ChargeMethod.INTEREST))
-      return Optional.of(ChargeProportionalDesignator.NOT_PROPORTIONAL);
-
-    return ChargeProportionalDesignator.fromString(scheduledCharge.getChargeDefinition().getProportionalTo());
-  }
-
   private static Function<BigDecimal, BigDecimal> howToApplyScheduledChargeToAmount(
       final ScheduledCharge scheduledCharge, final BigDecimal interest)
   {
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
index 6320deb..e836004 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/IndividualLoanService.java
@@ -18,9 +18,6 @@
 import io.mifos.individuallending.api.v1.domain.caseinstance.ChargeName;
 import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPayment;
 import io.mifos.individuallending.api.v1.domain.caseinstance.PlannedPaymentPage;
-import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
-import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
-import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -30,18 +27,17 @@
 import java.util.*;
 import java.util.stream.Collector;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
  */
 @Service
 public class IndividualLoanService {
-  private final ChargeDefinitionService chargeDefinitionService;
+  private final ScheduledChargesService scheduledChargesService;
 
   @Autowired
-  public IndividualLoanService(final ChargeDefinitionService chargeDefinitionService) {
-    this.chargeDefinitionService = chargeDefinitionService;
+  public IndividualLoanService(final ScheduledChargesService scheduledChargesService) {
+    this.scheduledChargesService = scheduledChargesService;
   }
 
   public PlannedPaymentPage getPlannedPaymentsPage(
@@ -53,7 +49,7 @@
 
     final List<ScheduledAction> scheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(initialDisbursalDate, dataContextOfAction.getCaseParameters());
 
-    final List<ScheduledCharge> scheduledCharges = getScheduledCharges(dataContextOfAction.getProduct().getIdentifier(), scheduledActions);
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(dataContextOfAction.getProduct().getIdentifier(), scheduledActions);
 
     final BigDecimal loanPaymentSize = CostComponentService.getLoanPaymentSize(
         dataContextOfAction.getCaseParameters().getMaximumBalance(),
@@ -98,21 +94,6 @@
     return new ChargeName(scheduledCharge.getChargeDefinition().getIdentifier(), scheduledCharge.getChargeDefinition().getName());
   }
 
-  List<ScheduledCharge> getScheduledCharges(
-      final String productIdentifier,
-      final @Nonnull List<ScheduledAction> scheduledActions) {
-    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByChargeAction
-            = chargeDefinitionService.getChargeDefinitionsMappedByChargeAction(productIdentifier);
-
-    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction
-            = chargeDefinitionService.getChargeDefinitionsMappedByAccrueAction(productIdentifier);
-
-    return getScheduledCharges(
-            scheduledActions,
-            chargeDefinitionsMappedByChargeAction,
-            chargeDefinitionsMappedByAccrueAction);
-  }
-
   static private List<PlannedPayment> getPlannedPaymentsElements(
       final BigDecimal initialBalance,
       final int minorCurrencyUnitDigits,
@@ -124,7 +105,7 @@
         .collect(Collectors.groupingBy(IndividualLoanService::getPeriodFromScheduledCharge,
             Collectors.mapping(x -> x,
                 Collector.of(
-                    () -> new TreeSet<>(new ScheduledChargeComparator()),
+                    () -> new TreeSet<>(new ScheduledChargesService.ScheduledChargeComparator()),
                     SortedSet::add,
                     (left, right) -> { left.addAll(right); return left; }))));
 
@@ -179,72 +160,4 @@
     else
       return scheduledAction.repaymentPeriod;
   }
-
-  static List<ScheduledCharge> getScheduledCharges(final List<ScheduledAction> scheduledActions,
-                                                    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByChargeAction,
-                                                    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction) {
-    return scheduledActions.stream()
-        .flatMap(scheduledAction ->
-            getChargeDefinitionStream(
-                chargeDefinitionsMappedByChargeAction,
-                chargeDefinitionsMappedByAccrueAction,
-                scheduledAction)
-                .map(chargeDefinition -> new ScheduledCharge(scheduledAction, chargeDefinition)))
-        .collect(Collectors.toList());
-  }
-
-  private static Stream<ChargeDefinition> getChargeDefinitionStream(
-          final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByChargeAction,
-          final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction,
-          final ScheduledAction scheduledAction) {
-    final List<ChargeDefinition> chargeMappingList = chargeDefinitionsMappedByChargeAction
-        .get(scheduledAction.action.name());
-    Stream<ChargeDefinition> chargeMapping = chargeMappingList == null ? Stream.empty() : chargeMappingList.stream();
-    if (chargeMapping == null)
-      chargeMapping = Stream.empty();
-
-    final List<ChargeDefinition> accrueMappingList = chargeDefinitionsMappedByAccrueAction
-        .get(scheduledAction.action.name());
-    Stream<ChargeDefinition> accrueMapping = accrueMappingList == null ? Stream.empty() : accrueMappingList.stream();
-    if (accrueMapping == null)
-      accrueMapping = Stream.empty();
-
-    return Stream.concat(
-        accrueMapping.sorted(IndividualLoanService::proportionalityApplicationOrder),
-        chargeMapping.sorted(IndividualLoanService::proportionalityApplicationOrder));
-  }
-
-  private static class ScheduledChargeComparator implements Comparator<ScheduledCharge>
-  {
-    @Override
-    public int compare(ScheduledCharge o1, ScheduledCharge o2) {
-      int ret = o1.getScheduledAction().when.compareTo(o2.getScheduledAction().when);
-      if (ret == 0)
-        ret = o1.getScheduledAction().action.compareTo(o2.getScheduledAction().action);
-      if (ret == 0)
-        ret = proportionalityApplicationOrder(o1.getChargeDefinition(), o2.getChargeDefinition());
-      if (ret == 0)
-        return o1.getChargeDefinition().getIdentifier().compareTo(o2.getChargeDefinition().getIdentifier());
-      else
-        return ret;
-    }
-  }
-
-  private static int proportionalityApplicationOrder(final ChargeDefinition o1, final ChargeDefinition o2) {
-    final Optional<ChargeProportionalDesignator> aProportionalToDesignator
-        = ChargeProportionalDesignator.fromString(o1.getProportionalTo());
-    final Optional<ChargeProportionalDesignator> bProportionalToDesignator
-        = ChargeProportionalDesignator.fromString(o2.getProportionalTo());
-
-    if (aProportionalToDesignator.isPresent() && bProportionalToDesignator.isPresent())
-      return Integer.compare(
-          aProportionalToDesignator.get().getOrderOfApplication(),
-          bProportionalToDesignator.get().getOrderOfApplication());
-    else if (aProportionalToDesignator.isPresent())
-      return 1;
-    else if (bProportionalToDesignator.isPresent())
-      return -1;
-    else
-      return 0;
-  }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledCharge.java b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledCharge.java
index 60fb56c..7b98317 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledCharge.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledCharge.java
@@ -18,6 +18,7 @@
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 
 import javax.annotation.Nonnull;
+import java.util.Optional;
 
 /**
  * @author Myrle Krantz
@@ -25,10 +26,16 @@
 public class ScheduledCharge {
   private final ScheduledAction scheduledAction;
   private final ChargeDefinition chargeDefinition;
+  @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+  private final Optional<ChargeRange> chargeRange;
 
-  ScheduledCharge(@Nonnull final ScheduledAction scheduledAction, @Nonnull final ChargeDefinition chargeDefinition) {
+  ScheduledCharge(
+      @Nonnull final ScheduledAction scheduledAction,
+      @Nonnull final ChargeDefinition chargeDefinition,
+      @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Nonnull final Optional<ChargeRange> chargeRange) {
     this.scheduledAction = scheduledAction;
     this.chargeDefinition = chargeDefinition;
+    this.chargeRange = chargeRange;
   }
 
   ScheduledAction getScheduledAction() {
@@ -39,11 +46,16 @@
     return chargeDefinition;
   }
 
+  Optional<ChargeRange> getChargeRange() {
+    return chargeRange;
+  }
+
   @Override
   public String toString() {
     return "ScheduledCharge{" +
-            "scheduledAction=" + scheduledAction +
-            ", chargeDefinition=" + chargeDefinition +
-            '}';
+        "scheduledAction=" + scheduledAction +
+        ", chargeDefinition=" + chargeDefinition +
+        ", chargeRange=" + chargeRange +
+        '}';
   }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargesService.java b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargesService.java
new file mode 100644
index 0000000..fcbfc6c
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ScheduledChargesService.java
@@ -0,0 +1,191 @@
+/*
+ * 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.individuallending.internal.service;
+
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentEntity;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentRepository;
+import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import java.math.BigDecimal;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ScheduledChargesService {
+  private final ChargeDefinitionService chargeDefinitionService;
+  private final BalanceSegmentRepository balanceSegmentRepository;
+
+  @Autowired
+  public ScheduledChargesService(
+      final ChargeDefinitionService chargeDefinitionService,
+      final BalanceSegmentRepository balanceSegmentRepository) {
+    this.chargeDefinitionService = chargeDefinitionService;
+    this.balanceSegmentRepository = balanceSegmentRepository;
+  }
+
+  List<ScheduledCharge> getScheduledCharges(
+      final String productIdentifier,
+      final @Nonnull List<ScheduledAction> scheduledActions) {
+    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByChargeAction
+        = chargeDefinitionService.getChargeDefinitionsMappedByChargeAction(productIdentifier);
+
+    final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction
+        = chargeDefinitionService.getChargeDefinitionsMappedByAccrueAction(productIdentifier);
+
+    return getScheduledCharges(
+        productIdentifier,
+        scheduledActions,
+        chargeDefinitionsMappedByChargeAction,
+        chargeDefinitionsMappedByAccrueAction);
+  }
+
+  private List<ScheduledCharge> getScheduledCharges(
+      final String productIdentifier,
+      final List<ScheduledAction> scheduledActions,
+      final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByChargeAction,
+      final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction) {
+    return scheduledActions.stream()
+        .flatMap(scheduledAction ->
+            getChargeDefinitionStream(
+                chargeDefinitionsMappedByChargeAction,
+                chargeDefinitionsMappedByAccrueAction,
+                scheduledAction)
+                .map(chargeDefinition -> new ScheduledCharge(
+                    scheduledAction,
+                    chargeDefinition,
+                    findChargeRange(productIdentifier, chargeDefinition))))
+        .collect(Collectors.toList());
+  }
+
+  @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+  private static class Segment {
+    final String identifier;
+    final BigDecimal lowerBound;
+    final Optional<BigDecimal> upperBound;
+
+    private Segment(final String segmentIdentifier,
+            final BigDecimal lowerBound,
+            final Optional<BigDecimal> upperBound) {
+      this.identifier = segmentIdentifier;
+      this.lowerBound = lowerBound;
+      this.upperBound = upperBound;
+    }
+
+    BigDecimal getLowerBound() {
+      return lowerBound;
+    }
+
+    Optional<BigDecimal> getUpperBound() {
+      return upperBound;
+    }
+  }
+
+  Optional<ChargeRange> findChargeRange(final String productIdentifier, final ChargeDefinition chargeDefinition) {
+    if ((chargeDefinition.getForSegmentSet() == null) ||
+        (chargeDefinition.getFromSegment() == null) ||
+        (chargeDefinition.getToSegment() == null))
+      return Optional.empty();
+
+    final List<BalanceSegmentEntity> segmentSet = balanceSegmentRepository.findByProductIdentifierAndSegmentSetIdentifier(productIdentifier, chargeDefinition.getForSegmentSet())
+        .sorted(Comparator.comparing(BalanceSegmentEntity::getLowerBound))
+        .collect(Collectors.toList());
+
+    final Map<String, Segment> segments = Stream.iterate(0, i -> i + 1).limit(segmentSet.size())
+        .map(i -> new Segment(
+            segmentSet.get(i).getSegmentIdentifier(),
+            segmentSet.get(i).getLowerBound(),
+            Optional.ofNullable(i + 1 < segmentSet.size() ?
+                segmentSet.get(i + 1).getLowerBound() :
+                null)
+        ))
+        .collect(Collectors.toMap(x -> x.identifier, x -> x));
+
+
+    final Optional<Segment> fromSegment = Optional.ofNullable(segments.get(chargeDefinition.getFromSegment()));
+    final Optional<Segment> toSegment = Optional.ofNullable(segments.get(chargeDefinition.getToSegment()));
+    if (!fromSegment.isPresent() || !toSegment.isPresent())
+      return Optional.empty();
+
+    return Optional.of(new ChargeRange(fromSegment.get().getLowerBound(), toSegment.get().getUpperBound()));
+  }
+
+  private static Stream<ChargeDefinition> getChargeDefinitionStream(
+      final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByChargeAction,
+      final Map<String, List<ChargeDefinition>> chargeDefinitionsMappedByAccrueAction,
+      final ScheduledAction scheduledAction) {
+    final List<ChargeDefinition> chargeMappingList = chargeDefinitionsMappedByChargeAction
+        .get(scheduledAction.action.name());
+    Stream<ChargeDefinition> chargeMapping = chargeMappingList == null ? Stream.empty() : chargeMappingList.stream();
+    if (chargeMapping == null)
+      chargeMapping = Stream.empty();
+
+    final List<ChargeDefinition> accrueMappingList = chargeDefinitionsMappedByAccrueAction
+        .get(scheduledAction.action.name());
+    Stream<ChargeDefinition> accrueMapping = accrueMappingList == null ? Stream.empty() : accrueMappingList.stream();
+    if (accrueMapping == null)
+      accrueMapping = Stream.empty();
+
+    return Stream.concat(
+        accrueMapping.sorted(ScheduledChargesService::proportionalityApplicationOrder),
+        chargeMapping.sorted(ScheduledChargesService::proportionalityApplicationOrder));
+  }
+
+  static class ScheduledChargeComparator implements Comparator<ScheduledCharge>
+  {
+    @Override
+    public int compare(ScheduledCharge o1, ScheduledCharge o2) {
+      int ret = o1.getScheduledAction().when.compareTo(o2.getScheduledAction().when);
+      if (ret == 0)
+        ret = o1.getScheduledAction().action.compareTo(o2.getScheduledAction().action);
+      if (ret == 0)
+        ret = proportionalityApplicationOrder(o1.getChargeDefinition(), o2.getChargeDefinition());
+      if (ret == 0)
+        return o1.getChargeDefinition().getIdentifier().compareTo(o2.getChargeDefinition().getIdentifier());
+      else
+        return ret;
+    }
+  }
+
+  private static int proportionalityApplicationOrder(final ChargeDefinition o1, final ChargeDefinition o2) {
+    final Optional<ChargeProportionalDesignator> aProportionalToDesignator
+        = ChargeProportionalDesignator.fromString(o1.getProportionalTo());
+    final Optional<ChargeProportionalDesignator> bProportionalToDesignator
+        = ChargeProportionalDesignator.fromString(o2.getProportionalTo());
+
+    if (aProportionalToDesignator.isPresent() && bProportionalToDesignator.isPresent())
+      return Integer.compare(
+          aProportionalToDesignator.get().getOrderOfApplication(),
+          bProportionalToDesignator.get().getOrderOfApplication());
+    else if (aProportionalToDesignator.isPresent())
+      return 1;
+    else if (bProportionalToDesignator.isPresent())
+      return -1;
+    else
+      return 0;
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/ChangeBalanceSegmentSetCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/ChangeBalanceSegmentSetCommand.java
new file mode 100644
index 0000000..19edb4c
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/ChangeBalanceSegmentSetCommand.java
@@ -0,0 +1,63 @@
+/*
+ * 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.service.internal.command;
+
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ChangeBalanceSegmentSetCommand {
+  private final String productIdentifier;
+  private final BalanceSegmentSet instance;
+
+  public ChangeBalanceSegmentSetCommand(String productIdentifier, BalanceSegmentSet instance) {
+    this.productIdentifier = productIdentifier;
+    this.instance = instance;
+  }
+
+  public String getProductIdentifier() {
+    return productIdentifier;
+  }
+
+  public BalanceSegmentSet getInstance() {
+    return instance;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ChangeBalanceSegmentSetCommand that = (ChangeBalanceSegmentSetCommand) o;
+    return Objects.equals(productIdentifier, that.productIdentifier) &&
+        Objects.equals(instance, that.instance);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(productIdentifier, instance);
+  }
+
+  @Override
+  public String toString() {
+    return "ChangeBalanceSegmentSetCommand{" +
+        "productIdentifier='" + productIdentifier + '\'' +
+        ", instance=" + instance +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/CreateBalanceSegmentSetCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/CreateBalanceSegmentSetCommand.java
new file mode 100644
index 0000000..a5a4925
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/CreateBalanceSegmentSetCommand.java
@@ -0,0 +1,47 @@
+/*
+ * 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.service.internal.command;
+
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+
+/**
+ * @author Myrle Krantz
+ */
+public class CreateBalanceSegmentSetCommand {
+  private final String productIdentifier;
+  private final BalanceSegmentSet instance;
+
+  public CreateBalanceSegmentSetCommand(String productIdentifier, BalanceSegmentSet instance) {
+    this.productIdentifier = productIdentifier;
+    this.instance = instance;
+  }
+
+  public String getProductIdentifier() {
+    return productIdentifier;
+  }
+
+  public BalanceSegmentSet getInstance() {
+    return instance;
+  }
+
+  @Override
+  public String toString() {
+    return "CreateBalanceSegmentSetCommand{" +
+        "productIdentifier='" + productIdentifier + '\'' +
+        ", instance=" + instance +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteBalanceSegmentSetCommand.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteBalanceSegmentSetCommand.java
new file mode 100644
index 0000000..cb4ac07
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/DeleteBalanceSegmentSetCommand.java
@@ -0,0 +1,45 @@
+/*
+ * 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.service.internal.command;
+
+/**
+ * @author Myrle Krantz
+ */
+public class DeleteBalanceSegmentSetCommand {
+  private final String productIdentifier;
+  private final String balanceSegmentSetIdentifier;
+
+  public DeleteBalanceSegmentSetCommand(String productIdentifier, String balanceSegmentSetIdentifier) {
+    this.productIdentifier = productIdentifier;
+    this.balanceSegmentSetIdentifier = balanceSegmentSetIdentifier;
+  }
+
+  public String getProductIdentifier() {
+    return productIdentifier;
+  }
+
+  public String getBalanceSegmentSetIdentifier() {
+    return balanceSegmentSetIdentifier;
+  }
+
+  @Override
+  public String toString() {
+    return "DeleteBalanceSegmentSetCommand{" +
+        "productIdentifier='" + productIdentifier + '\'' +
+        ", balanceSegmentSetIdentifier='" + balanceSegmentSetIdentifier + '\'' +
+        '}';
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/BalanceSegmentSetCommandHandler.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/BalanceSegmentSetCommandHandler.java
new file mode 100644
index 0000000..ae83771
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/BalanceSegmentSetCommandHandler.java
@@ -0,0 +1,119 @@
+/*
+ * 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.service.internal.command.handler;
+
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.CommandLogLevel;
+import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.portfolio.api.v1.events.BalanceSegmentSetEvent;
+import io.mifos.portfolio.api.v1.events.EventConstants;
+import io.mifos.portfolio.service.internal.command.ChangeBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.command.CreateBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.command.DeleteBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.mapper.BalanceSegmentSetMapper;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentEntity;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentRepository;
+import io.mifos.portfolio.service.internal.repository.ProductEntity;
+import io.mifos.portfolio.service.internal.repository.ProductRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Myrle Krantz
+ */
+@Aggregate
+public class BalanceSegmentSetCommandHandler {
+  private final BalanceSegmentRepository balanceSegmentRepository;
+  private final ProductRepository productRepository;
+
+  @Autowired
+  public BalanceSegmentSetCommandHandler(
+      final BalanceSegmentRepository balanceSegmentRepository,
+      final ProductRepository productRepository) {
+    this.balanceSegmentRepository = balanceSegmentRepository;
+    this.productRepository = productRepository;
+  }
+
+  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.POST_BALANCE_SEGMENT_SET)
+  public BalanceSegmentSetEvent process(final CreateBalanceSegmentSetCommand createBalanceSegmentSetCommand) {
+    final ProductEntity product = productRepository.findByIdentifier(createBalanceSegmentSetCommand.getProductIdentifier())
+        .orElseThrow(() -> ServiceException.notFound("Product with identifier ''{0}'' doesn''t exist.", createBalanceSegmentSetCommand.getProductIdentifier()));
+
+    final List<BalanceSegmentEntity> balanceSegmentSetEntities = BalanceSegmentSetMapper.map(
+        createBalanceSegmentSetCommand.getInstance(), product);
+
+    balanceSegmentRepository.save(balanceSegmentSetEntities);
+
+    return new BalanceSegmentSetEvent(
+        createBalanceSegmentSetCommand.getProductIdentifier(),
+        createBalanceSegmentSetCommand.getInstance().getIdentifier());
+  }
+
+  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.PUT_BALANCE_SEGMENT_SET)
+  public BalanceSegmentSetEvent process(final ChangeBalanceSegmentSetCommand changeBalanceSegmentSetCommand) {
+    final ProductEntity product = productRepository.findByIdentifier(changeBalanceSegmentSetCommand.getProductIdentifier())
+        .orElseThrow(() -> ServiceException.notFound("Product with identifier ''{0}'' doesn''t exist.", changeBalanceSegmentSetCommand.getProductIdentifier()));
+
+    final List<BalanceSegmentEntity> balanceSegmentSets = balanceSegmentRepository.findByProductIdentifierAndSegmentSetIdentifier(
+        changeBalanceSegmentSetCommand.getProductIdentifier(),
+        changeBalanceSegmentSetCommand.getInstance().getIdentifier())
+        .collect(Collectors.toList());
+    if (balanceSegmentSets.isEmpty())
+      throw ServiceException.notFound("Segment set with identifier ''{0}.{1}'' doesn''t exist.",
+          changeBalanceSegmentSetCommand.getProductIdentifier(),
+          changeBalanceSegmentSetCommand.getInstance().getIdentifier());
+
+    balanceSegmentRepository.deleteInBatch(balanceSegmentSets);
+
+    final List<BalanceSegmentEntity> balanceSegmentSetEntities = BalanceSegmentSetMapper.map(
+        changeBalanceSegmentSetCommand.getInstance(), product);
+
+    balanceSegmentRepository.save(balanceSegmentSetEntities);
+
+    return new BalanceSegmentSetEvent(
+        changeBalanceSegmentSetCommand.getProductIdentifier(),
+        changeBalanceSegmentSetCommand.getInstance().getIdentifier());
+  }
+
+  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.DELETE_BALANCE_SEGMENT_SET)
+  public BalanceSegmentSetEvent process(final DeleteBalanceSegmentSetCommand deleteBalanceSegmentSetCommand) {
+    final List<BalanceSegmentEntity> balanceSegmentSets = balanceSegmentRepository.findByProductIdentifierAndSegmentSetIdentifier(
+        deleteBalanceSegmentSetCommand.getProductIdentifier(),
+        deleteBalanceSegmentSetCommand.getBalanceSegmentSetIdentifier())
+        .collect(Collectors.toList());
+    if (balanceSegmentSets.isEmpty())
+      throw ServiceException.notFound("Segment set with identifier ''{0}.{1}'' doesn''t exist.",
+          deleteBalanceSegmentSetCommand.getProductIdentifier(),
+          deleteBalanceSegmentSetCommand.getBalanceSegmentSetIdentifier());
+
+    balanceSegmentRepository.deleteInBatch(balanceSegmentSets);
+
+    return new BalanceSegmentSetEvent(
+        deleteBalanceSegmentSetCommand.getProductIdentifier(),
+        deleteBalanceSegmentSetCommand.getBalanceSegmentSetIdentifier());
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ChargeDefinitionCommandHandler.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ChargeDefinitionCommandHandler.java
index 61b7db9..6cfb5a1 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ChargeDefinitionCommandHandler.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ChargeDefinitionCommandHandler.java
@@ -15,7 +15,11 @@
  */
 package io.mifos.portfolio.service.internal.command.handler;
 
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
 import io.mifos.core.command.annotation.CommandLogLevel;
+import io.mifos.core.command.annotation.EventEmitter;
+import io.mifos.core.lang.ServiceException;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.events.ChargeDefinitionEvent;
 import io.mifos.portfolio.api.v1.events.EventConstants;
@@ -23,17 +27,12 @@
 import io.mifos.portfolio.service.internal.command.CreateChargeDefinitionCommand;
 import io.mifos.portfolio.service.internal.command.DeleteProductChargeDefinitionCommand;
 import io.mifos.portfolio.service.internal.mapper.ChargeDefinitionMapper;
-import io.mifos.portfolio.service.internal.repository.ChargeDefinitionEntity;
-import io.mifos.portfolio.service.internal.repository.ChargeDefinitionRepository;
-import io.mifos.portfolio.service.internal.repository.ProductEntity;
-import io.mifos.portfolio.service.internal.repository.ProductRepository;
-import io.mifos.core.command.annotation.Aggregate;
-import io.mifos.core.command.annotation.CommandHandler;
-import io.mifos.core.command.annotation.EventEmitter;
-import io.mifos.core.lang.ServiceException;
+import io.mifos.portfolio.service.internal.repository.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Optional;
+
 /**
  * @author Myrle Krantz
  */
@@ -41,13 +40,16 @@
 public class ChargeDefinitionCommandHandler {
   private final ProductRepository productRepository;
   private final ChargeDefinitionRepository chargeDefinitionRepository;
+  private final BalanceSegmentRepository balanceSegmentRepository;
 
   @Autowired
   public ChargeDefinitionCommandHandler(
-          final ProductRepository productRepository,
-          final ChargeDefinitionRepository chargeDefinitionRepository) {
+      final ProductRepository productRepository,
+      final ChargeDefinitionRepository chargeDefinitionRepository,
+      final BalanceSegmentRepository balanceSegmentRepository) {
     this.productRepository = productRepository;
     this.chargeDefinitionRepository = chargeDefinitionRepository;
+    this.balanceSegmentRepository = balanceSegmentRepository;
   }
 
   @SuppressWarnings("unused")
@@ -62,8 +64,10 @@
             = productRepository.findByIdentifier(productIdentifier)
             .orElseThrow(() -> ServiceException.badRequest("The given product identifier does not refer to a product {0}", productIdentifier));
 
+    final SegmentRange segmentRange = getSegmentRange(chargeDefinition, productIdentifier);
+
     final ChargeDefinitionEntity chargeDefinitionEntity =
-            ChargeDefinitionMapper.map(productEntity, chargeDefinition);
+            ChargeDefinitionMapper.map(productEntity, chargeDefinition, segmentRange.fromSegment, segmentRange.toSegment);
     chargeDefinitionRepository.save(chargeDefinitionEntity);
 
     return new ChargeDefinitionEvent(
@@ -83,8 +87,10 @@
             = chargeDefinitionRepository.findByProductIdAndChargeDefinitionIdentifier(productIdentifier, chargeDefinition.getIdentifier())
             .orElseThrow(() -> ServiceException.internalError("task definition not found."));
 
+    final SegmentRange segmentRange = getSegmentRange(chargeDefinition, productIdentifier);
+
     final ChargeDefinitionEntity chargeDefinitionEntity =
-            ChargeDefinitionMapper.map(existingChargeDefinition.getProduct(), chargeDefinition);
+            ChargeDefinitionMapper.map(existingChargeDefinition.getProduct(), chargeDefinition, segmentRange.fromSegment, segmentRange.toSegment);
     chargeDefinitionEntity.setId(existingChargeDefinition.getId());
     chargeDefinitionEntity.setId(existingChargeDefinition.getId());
     chargeDefinitionRepository.save(chargeDefinitionEntity);
@@ -112,4 +118,34 @@
             command.getChargeDefinitionIdentifier());
   }
 
+  static class SegmentRange {
+    final BalanceSegmentEntity fromSegment;
+    final BalanceSegmentEntity toSegment;
+
+    SegmentRange(final BalanceSegmentEntity fromSegment, final BalanceSegmentEntity toSegment) {
+      this.fromSegment = fromSegment;
+      this.toSegment = toSegment;
+    }
+  }
+
+  private SegmentRange getSegmentRange(final ChargeDefinition chargeDefinition, final String productIdentifier) {
+    if (chargeDefinition.getForSegmentSet() != null) {
+      final Optional<BalanceSegmentEntity> fromSegmentOptional =
+          balanceSegmentRepository.findByProductIdentifierAndSegmentSetIdentifierAndSegmentIdentifier(
+              productIdentifier,
+              chargeDefinition.getForSegmentSet(),
+              chargeDefinition.getFromSegment());
+      final Optional<BalanceSegmentEntity> toSegmentOptional =
+          balanceSegmentRepository.findByProductIdentifierAndSegmentSetIdentifierAndSegmentIdentifier(
+              productIdentifier,
+              chargeDefinition.getForSegmentSet(),
+              chargeDefinition.getFromSegment());
+
+      if (fromSegmentOptional.isPresent() && toSegmentOptional.isPresent()) {
+        return new SegmentRange(fromSegmentOptional.get(), toSegmentOptional.get());
+      }
+    }
+
+    return new SegmentRange(null, null);
+  }
 }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java
index 6693e03..c075416 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/command/handler/ProductCommandHandler.java
@@ -85,7 +85,7 @@
 
   private void createChargeDefinition(final ProductEntity productEntity, final ChargeDefinition chargeDefinition) {
     final ChargeDefinitionEntity chargeDefinitionEntity =
-            ChargeDefinitionMapper.map(productEntity, chargeDefinition);
+            ChargeDefinitionMapper.map(productEntity, chargeDefinition, null, null);
     chargeDefinitionRepository.save(chargeDefinitionEntity);
   }
 
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/BalanceSegmentSetMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/BalanceSegmentSetMapper.java
new file mode 100644
index 0000000..63ba34f
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/BalanceSegmentSetMapper.java
@@ -0,0 +1,62 @@
+/*
+ * 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.service.internal.mapper;
+
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentEntity;
+import io.mifos.portfolio.service.internal.repository.ProductEntity;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+public class BalanceSegmentSetMapper {
+  public static List<BalanceSegmentEntity> map(final BalanceSegmentSet instance, final ProductEntity product) {
+    return
+        Stream.iterate(0, i -> i+1).limit(instance.getSegmentIdentifiers().size())
+            .map(i -> {
+              final BalanceSegmentEntity ret = new BalanceSegmentEntity();
+              ret.setProduct(product);
+              ret.setSegmentSetIdentifier(instance.getIdentifier());
+              ret.setSegmentIdentifier(instance.getSegmentIdentifiers().get(i));
+              ret.setLowerBound(instance.getSegments().get(i));
+              return ret;
+            })
+            .collect(Collectors.toList());
+  }
+
+  public static Optional<BalanceSegmentSet> map(final Stream<BalanceSegmentEntity> instances) {
+    final BalanceSegmentSet ret = new BalanceSegmentSet();
+    ret.setSegments(new ArrayList<>());
+    ret.setSegmentIdentifiers(new ArrayList<>());
+    instances.sorted(Comparator.comparing(BalanceSegmentEntity::getLowerBound))
+        .forEach(seg -> {
+          ret.setIdentifier(seg.getSegmentSetIdentifier());
+          ret.getSegments().add(seg.getLowerBound());
+          ret.getSegmentIdentifiers().add(seg.getSegmentIdentifier());
+        });
+    if (ret.getSegments().isEmpty())
+      return Optional.empty();
+    else
+      return Optional.of(ret);
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
index 91958f5..f99d272 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/mapper/ChargeDefinitionMapper.java
@@ -17,9 +17,11 @@
 
 import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentEntity;
 import io.mifos.portfolio.service.internal.repository.ChargeDefinitionEntity;
 import io.mifos.portfolio.service.internal.repository.ProductEntity;
 
+import javax.annotation.Nullable;
 import java.util.Optional;
 
 import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
@@ -28,7 +30,11 @@
  * @author Myrle Krantz
  */
 public class ChargeDefinitionMapper {
-  public static ChargeDefinitionEntity map(final ProductEntity productEntity, final ChargeDefinition chargeDefinition) {
+  public static ChargeDefinitionEntity map(
+      final ProductEntity productEntity,
+      final ChargeDefinition chargeDefinition,
+      @Nullable final BalanceSegmentEntity fromSegment,
+      @Nullable final BalanceSegmentEntity toSegment) {
 
     final ChargeDefinitionEntity ret = new ChargeDefinitionEntity();
 
@@ -46,6 +52,11 @@
     ret.setAccrualAccountDesignator(chargeDefinition.getAccrualAccountDesignator());
     ret.setToAccountDesignator(chargeDefinition.getToAccountDesignator());
     ret.setReadOnly(chargeDefinition.isReadOnly());
+    if (fromSegment != null && toSegment != null) {
+      ret.setSegmentSet(fromSegment.getSegmentSetIdentifier());
+      ret.setFromSegment(fromSegment.getSegmentIdentifier());
+      ret.setToSegment(toSegment.getSegmentIdentifier());
+    }
 
     return ret;
   }
@@ -66,6 +77,11 @@
     ret.setAccrualAccountDesignator(from.getAccrualAccountDesignator());
     ret.setToAccountDesignator(from.getToAccountDesignator());
     ret.setReadOnly(Optional.ofNullable(from.getReadOnly()).orElseGet(() -> readOnlyLegacyMapper(from.getIdentifier())));
+    if (from.getSegmentSet() != null && from.getFromSegment() != null && from.getToSegment() != null) {
+      ret.setForSegmentSet(from.getSegmentSet());
+      ret.setFromSegment(from.getFromSegment());
+      ret.setToSegment(from.getToSegment());
+    }
 
     return ret;
   }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentEntity.java
new file mode 100644
index 0000000..a395c02
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentEntity.java
@@ -0,0 +1,110 @@
+/*
+ * 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.service.internal.repository;
+
+import javax.persistence.*;
+import java.math.BigDecimal;
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@Entity
+@Table(name = "bastet_p_balance_segs")
+public class BalanceSegmentEntity {
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+
+  @ManyToOne(fetch = FetchType.LAZY)
+  @JoinColumn(name = "product_id", nullable = false)
+  private ProductEntity product;
+
+  @Column(name = "seg_set_identifier", nullable = false)
+  private String segmentSetIdentifier;
+
+  @Column(name = "segment_identifier", nullable = false)
+  private String segmentIdentifier;
+
+  @Column(name = "lower_bound")
+  private BigDecimal lowerBound;
+
+  public BalanceSegmentEntity() {
+  }
+
+  public BalanceSegmentEntity(ProductEntity product, String segmentSetIdentifier, String segmentIdentifier, BigDecimal lowerBound) {
+    this.product = product;
+    this.segmentSetIdentifier = segmentSetIdentifier;
+    this.segmentIdentifier = segmentIdentifier;
+    this.lowerBound = lowerBound;
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public ProductEntity getProduct() {
+    return product;
+  }
+
+  public void setProduct(ProductEntity product) {
+    this.product = product;
+  }
+
+  public String getSegmentSetIdentifier() {
+    return segmentSetIdentifier;
+  }
+
+  public void setSegmentSetIdentifier(String segmentSetIdentifier) {
+    this.segmentSetIdentifier = segmentSetIdentifier;
+  }
+
+  public String getSegmentIdentifier() {
+    return segmentIdentifier;
+  }
+
+  public void setSegmentIdentifier(String segmentIdentifier) {
+    this.segmentIdentifier = segmentIdentifier;
+  }
+
+  public BigDecimal getLowerBound() {
+    return lowerBound;
+  }
+
+  public void setLowerBound(BigDecimal lowerBound) {
+    this.lowerBound = lowerBound;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    BalanceSegmentEntity that = (BalanceSegmentEntity) o;
+    return Objects.equals(product, that.product) &&
+        Objects.equals(segmentSetIdentifier, that.segmentSetIdentifier) &&
+        Objects.equals(segmentIdentifier, that.segmentIdentifier);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(product, segmentSetIdentifier, segmentIdentifier);
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentRepository.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentRepository.java
new file mode 100644
index 0000000..0a4f903
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/BalanceSegmentRepository.java
@@ -0,0 +1,32 @@
+/*
+ * 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.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Repository
+public interface BalanceSegmentRepository extends JpaRepository<BalanceSegmentEntity, Long> {
+  Stream<BalanceSegmentEntity> findByProductIdentifierAndSegmentSetIdentifier(String productIdentifier, String segmentSetIdentifier);
+
+  Optional<BalanceSegmentEntity> findByProductIdentifierAndSegmentSetIdentifierAndSegmentIdentifier(String productIdentifier, String segmentSetIdentifier, String segmentIdentifier);
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
index 423c871..11e154e 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionEntity.java
@@ -79,6 +79,15 @@
   @Column(name = "read_only")
   private Boolean readOnly;
 
+  @Column(name = "segment_set")
+  private String segmentSet;
+
+  @Column(name = "from_segment")
+  private String fromSegment;
+
+  @Column(name = "to_segment")
+  private String toSegment;
+
   public ChargeDefinitionEntity() {
   }
 
@@ -202,6 +211,30 @@
     this.readOnly = readOnly;
   }
 
+  public String getSegmentSet() {
+    return segmentSet;
+  }
+
+  public void setSegmentSet(String segmentSet) {
+    this.segmentSet = segmentSet;
+  }
+
+  public String getFromSegment() {
+    return fromSegment;
+  }
+
+  public void setFromSegment(String fromSegment) {
+    this.fromSegment = fromSegment;
+  }
+
+  public String getToSegment() {
+    return toSegment;
+  }
+
+  public void setToSegment(String toSegment) {
+    this.toSegment = toSegment;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/BalanceSegmentSetService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/BalanceSegmentSetService.java
new file mode 100644
index 0000000..ee558a6
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/BalanceSegmentSetService.java
@@ -0,0 +1,53 @@
+/*
+ * 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.service.internal.service;
+
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+import io.mifos.portfolio.service.internal.mapper.BalanceSegmentSetMapper;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class BalanceSegmentSetService {
+  private final BalanceSegmentRepository balanceSegmentRepository;
+
+  @Autowired
+  public BalanceSegmentSetService(
+      final BalanceSegmentRepository balanceSegmentRepository) {
+    this.balanceSegmentRepository = balanceSegmentRepository;
+  }
+
+  public boolean existsByIdentifier(final String productIdentifier, final String balanceSegmentSetIdentifier)
+  {
+    //TODO: replace with existsBy once we've upgraded to spring data 1.11 or later.
+    return balanceSegmentRepository
+        .findByProductIdentifierAndSegmentSetIdentifier(productIdentifier, balanceSegmentSetIdentifier)
+        .findAny().isPresent();
+  }
+
+  public Optional<BalanceSegmentSet> findByIdentifier(
+      final String productIdentifier,
+      final String balanceSegmentSetIdentifier) {
+    return BalanceSegmentSetMapper.map(balanceSegmentRepository
+        .findByProductIdentifierAndSegmentSetIdentifier(productIdentifier, balanceSegmentSetIdentifier));
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/BalanceSegmentSetRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/BalanceSegmentSetRestController.java
new file mode 100644
index 0000000..a5ca9cc
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/BalanceSegmentSetRestController.java
@@ -0,0 +1,147 @@
+/*
+ * 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.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.core.lang.ServiceException;
+import io.mifos.portfolio.api.v1.PermittableGroupIds;
+import io.mifos.portfolio.api.v1.domain.BalanceSegmentSet;
+import io.mifos.portfolio.service.internal.command.ChangeBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.command.CreateBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.command.DeleteBalanceSegmentSetCommand;
+import io.mifos.portfolio.service.internal.service.BalanceSegmentSetService;
+import io.mifos.portfolio.service.internal.service.CaseService;
+import io.mifos.portfolio.service.internal.service.ProductService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+/**
+ * @author Myrle Krantz
+ */
+@RestController
+@RequestMapping("/products/{productidentifier}/balancesegmentsets/")
+public class BalanceSegmentSetRestController {
+
+  private final CommandGateway commandGateway;
+  private final ProductService productService;
+  private final BalanceSegmentSetService balanceSegmentSetService;
+  private final CaseService caseService;
+
+  public BalanceSegmentSetRestController(final CommandGateway commandGateway,
+                                         final ProductService productService,
+                                         final BalanceSegmentSetService balanceSegmentSetService,
+                                         final CaseService caseService) {
+    this.commandGateway = commandGateway;
+    this.productService = productService;
+    this.balanceSegmentSetService = balanceSegmentSetService;
+    this.caseService = caseService;
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
+  @RequestMapping(
+      method = RequestMethod.POST,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody
+  ResponseEntity<Void> createBalanceSegmentSet(
+      @PathVariable("productidentifier") final String productIdentifier,
+      @RequestBody @Valid final BalanceSegmentSet instance) {
+    checkThatProductExists(productIdentifier);
+    checkThatSegmentSetDoesntExist(productIdentifier, instance.getIdentifier());
+    checkProductChangeable(productIdentifier);
+
+    this.commandGateway.process(new CreateBalanceSegmentSetCommand(productIdentifier, instance));
+    return new ResponseEntity<>(HttpStatus.ACCEPTED);
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
+  @RequestMapping(
+      value = "{balancesegmentsetidentifier}",
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE)
+  public @ResponseBody BalanceSegmentSet getBalanceSegmentSet(
+      @PathVariable("productidentifier") final String productIdentifier,
+      @PathVariable("balancesegmentsetidentifier") final String balanceSegmentSetIdentifier) {
+    return balanceSegmentSetService.findByIdentifier(productIdentifier, balanceSegmentSetIdentifier)
+        .orElseThrow(() -> ServiceException.notFound(
+            "Segment set with identifier ''{0}.{1}'' doesn''t exist.", productIdentifier, balanceSegmentSetIdentifier));
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
+  @RequestMapping(
+      value = "{balancesegmentsetidentifier}",
+      method = RequestMethod.PUT,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE)
+  public @ResponseBody ResponseEntity<Void>  changeBalanceSegmentSet(
+      @PathVariable("productidentifier") final String productIdentifier,
+      @PathVariable("balancesegmentsetidentifier") final String balanceSegmentSetIdentifier,
+      @RequestBody @Valid BalanceSegmentSet balanceSegmentSet) {
+    checkThatProductAndBalanceSegmentSetExist(productIdentifier, balanceSegmentSetIdentifier);
+    checkProductChangeable(productIdentifier);
+
+    if (!balanceSegmentSetIdentifier.equals(balanceSegmentSet.getIdentifier()))
+      throw ServiceException.badRequest("Instance identifier may not be changed.");
+
+    this.commandGateway.process(new ChangeBalanceSegmentSetCommand(productIdentifier, balanceSegmentSet));
+    return new ResponseEntity<>(HttpStatus.ACCEPTED);
+  }
+
+  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
+  @RequestMapping(
+      value = "{balancesegmentsetidentifier}",
+      method = RequestMethod.DELETE,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public @ResponseBody ResponseEntity<Void>  deleteBalanceSegmentSet(
+      @PathVariable("productidentifier") final String productIdentifier,
+      @PathVariable("balancesegmentsetidentifier") final String balanceSegmentSetIdentifier) {
+    checkThatProductAndBalanceSegmentSetExist(productIdentifier, balanceSegmentSetIdentifier);
+    checkProductChangeable(productIdentifier);
+
+    this.commandGateway.process(new DeleteBalanceSegmentSetCommand(productIdentifier, balanceSegmentSetIdentifier));
+    return new ResponseEntity<>(HttpStatus.ACCEPTED);
+  }
+
+  private void checkThatProductExists(final String productIdentifier) {
+    if (!productService.existsByIdentifier(productIdentifier))
+      throw ServiceException.notFound("Product with identifier ''{0}'' doesn''t exist.", productIdentifier);
+  }
+
+  private void checkThatSegmentSetDoesntExist(final String productIdentifier, final String segmentSetIdentifier) {
+    if (balanceSegmentSetService.existsByIdentifier(productIdentifier, segmentSetIdentifier))
+      throw ServiceException.notFound("Segment set with identifier ''{0}.{1}'' already exists.", productIdentifier, segmentSetIdentifier);
+  }
+
+  private void checkThatProductAndBalanceSegmentSetExist(final String productIdentifier, final String segmentSetIdentifier) {
+    if (!balanceSegmentSetService.existsByIdentifier(productIdentifier, segmentSetIdentifier))
+      throw ServiceException.notFound("Segment set with identifier ''{0}.{1}'' doesn''t exist.", productIdentifier, segmentSetIdentifier);
+  }
+
+  private void checkProductChangeable(final String productIdentifier) {
+    if (caseService.existsByProductIdentifier(productIdentifier))
+      throw ServiceException.conflict("Cases exist for product with the identifier ''{0}''. Product cannot be changed.", productIdentifier);
+  }
+}
diff --git a/service/src/main/resources/db/migrations/mariadb/V7__balance_segment_sets.sql b/service/src/main/resources/db/migrations/mariadb/V7__balance_segment_sets.sql
new file mode 100644
index 0000000..a9a9399
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V7__balance_segment_sets.sql
@@ -0,0 +1,30 @@
+--
+-- 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.
+--
+
+CREATE TABLE bastet_p_balance_segs (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  seg_set_identifier       VARCHAR(32)    NOT NULL,
+  segment_identifier       VARCHAR(32)    NOT NULL,
+  product_id               BIGINT         NOT NULL,
+  lower_bound              DECIMAL(19,4)  NOT NULL,
+  CONSTRAINT bastet_p_balance_segs_pk PRIMARY KEY (id),
+  CONSTRAINT bastet_p_balance_segs_uq UNIQUE (product_id, seg_set_identifier, segment_identifier),
+  CONSTRAINT bastet_p_balance_segs_fk FOREIGN KEY (product_id) REFERENCES bastet_products (id)
+);
+
+ALTER TABLE bastet_p_chrg_defs ADD COLUMN segment_set VARCHAR(32) NULL DEFAULT NULL;
+ALTER TABLE bastet_p_chrg_defs ADD COLUMN from_segment VARCHAR(32) NULL DEFAULT NULL;
+ALTER TABLE bastet_p_chrg_defs ADD COLUMN to_segment VARCHAR(32) NULL DEFAULT NULL;
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ChargeRangeTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ChargeRangeTest.java
new file mode 100644
index 0000000..a581c78
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ChargeRangeTest.java
@@ -0,0 +1,25 @@
+package io.mifos.individuallending.internal.service;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.util.Optional;
+
+public class ChargeRangeTest {
+  @Test
+  public void amountIsWithinRange() throws Exception {
+    final ChargeRange testSubject1 = new ChargeRange(BigDecimal.TEN, Optional.empty());
+    Assert.assertFalse(testSubject1.amountIsWithinRange(BigDecimal.ZERO));
+    Assert.assertFalse(testSubject1.amountIsWithinRange(BigDecimal.ONE));
+    Assert.assertTrue(testSubject1.amountIsWithinRange(BigDecimal.TEN));
+    Assert.assertTrue(testSubject1.amountIsWithinRange(BigDecimal.TEN.add(BigDecimal.ONE)));
+
+    final ChargeRange testSubject2 = new ChargeRange(BigDecimal.ZERO, Optional.of(BigDecimal.TEN));
+    Assert.assertTrue(testSubject2.amountIsWithinRange(BigDecimal.ZERO));
+    Assert.assertTrue(testSubject2.amountIsWithinRange(BigDecimal.ONE));
+    Assert.assertFalse(testSubject2.amountIsWithinRange(BigDecimal.TEN));
+    Assert.assertFalse(testSubject2.amountIsWithinRange(BigDecimal.TEN.add(BigDecimal.ONE)));
+  }
+
+}
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java
new file mode 100644
index 0000000..eab6828
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/CostComponentServiceTest.java
@@ -0,0 +1,91 @@
+package io.mifos.individuallending.internal.service;
+
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import static io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator.PRINCIPAL_ADJUSTMENT_DESIGNATOR;
+import static io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR;
+
+@RunWith(Parameterized.class)
+public class CostComponentServiceTest {
+  private static class TestCase {
+    final String description;
+    ChargeProportionalDesignator chargeProportionalDesignator = ChargeProportionalDesignator.NOT_PROPORTIONAL;
+    BigDecimal maximumBalance = BigDecimal.ZERO;
+    BigDecimal runningBalance = BigDecimal.ZERO;
+    BigDecimal loanPaymentSize = BigDecimal.ZERO;
+    BigDecimal expectedAmount = BigDecimal.ZERO;
+
+    private TestCase(String description) {
+      this.description = description;
+    }
+
+    TestCase chargeProportionalDesignator(ChargeProportionalDesignator chargeProportionalDesignator) {
+      this.chargeProportionalDesignator = chargeProportionalDesignator;
+      return this;
+    }
+
+    TestCase maximumBalance(BigDecimal maximumBalance) {
+      this.maximumBalance = maximumBalance;
+      return this;
+    }
+
+    TestCase runningBalance(BigDecimal runningBalance) {
+      this.runningBalance = runningBalance;
+      return this;
+    }
+
+    TestCase loanPaymentSize(BigDecimal loanPaymentSize) {
+      this.loanPaymentSize = loanPaymentSize;
+      return this;
+    }
+
+    TestCase expectedAmount(BigDecimal expectedAmount) {
+      this.expectedAmount = expectedAmount;
+      return this;
+    }
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<CostComponentServiceTest.TestCase> ret = new ArrayList<>();
+    ret.add(new TestCase("simple"));
+    ret.add(new TestCase("distribution fee")
+        .chargeProportionalDesignator(PRINCIPAL_ADJUSTMENT_DESIGNATOR)
+        .maximumBalance(BigDecimal.valueOf(2000))
+        .loanPaymentSize(BigDecimal.valueOf(-2000))
+        .expectedAmount(BigDecimal.valueOf(2000)));
+    ret.add(new TestCase("origination fee")
+        .chargeProportionalDesignator(RUNNING_BALANCE_DESIGNATOR)
+        .runningBalance(BigDecimal.valueOf(5000))
+        .expectedAmount(BigDecimal.valueOf(5000)));
+    return ret;
+  }
+
+  private final CostComponentServiceTest.TestCase testCase;
+
+  public CostComponentServiceTest(final CostComponentServiceTest.TestCase testCase) {
+    this.testCase = testCase;
+  }
+
+  @Test
+  public void getAmountProportionalTo() {
+    final BigDecimal amount = CostComponentService.getAmountProportionalTo(
+        testCase.chargeProportionalDesignator,
+        testCase.maximumBalance,
+        testCase.runningBalance,
+        testCase.loanPaymentSize,
+        Collections.emptyMap());
+
+    Assert.assertEquals(testCase.expectedAmount, amount);
+  }
+
+}
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java b/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java
index 7f73106..92002b7 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/Fixture.java
@@ -28,6 +28,7 @@
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.Random;
 
 import static java.math.BigDecimal.ROUND_HALF_EVEN;
@@ -107,7 +108,7 @@
     chargeDefinition.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN);
     chargeDefinition.setAccrualAccountDesignator(AccountDesignators.INTEREST_ACCRUAL);
     chargeDefinition.setToAccountDesignator(AccountDesignators.INTEREST_INCOME);
-    return new ScheduledCharge(scheduledAction, chargeDefinition);
+    return new ScheduledCharge(scheduledAction, chargeDefinition, Optional.empty());
   }
 
   static Period getPeriod(final LocalDate initialDate, final int periodBeginDelta, final int periodLength) {
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
index 1bb13b1..fc31ae7 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/IndividualLoanServiceTest.java
@@ -24,6 +24,7 @@
 import io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.portfolio.api.v1.domain.*;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentRepository;
 import io.mifos.portfolio.service.internal.repository.CaseEntity;
 import io.mifos.portfolio.service.internal.repository.ProductEntity;
 import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
@@ -31,6 +32,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.mockito.Matchers;
 import org.mockito.Mockito;
 
 import java.math.BigDecimal;
@@ -47,6 +49,7 @@
  */
 @RunWith(Parameterized.class)
 public class IndividualLoanServiceTest {
+
   private static class ActionDatePair {
     final Action action;
     final LocalDate localDate;
@@ -171,6 +174,7 @@
 
   private final TestCase testCase;
   private final IndividualLoanService testSubject;
+  private final ScheduledChargesService scheduledChargesService;
   private final Map<String, List<ChargeDefinition>> chargeDefinitionsByChargeAction;
   private final Map<String, List<ChargeDefinition>> chargeDefinitionsByAccrueAction;
 
@@ -282,7 +286,12 @@
     Mockito.doReturn(chargeDefinitionsByChargeAction).when(chargeDefinitionServiceMock).getChargeDefinitionsMappedByChargeAction(testCase.productIdentifier);
     Mockito.doReturn(chargeDefinitionsByAccrueAction).when(chargeDefinitionServiceMock).getChargeDefinitionsMappedByAccrueAction(testCase.productIdentifier);
 
-    testSubject = new IndividualLoanService(chargeDefinitionServiceMock);
+    final BalanceSegmentRepository balanceSegmentRepositoryMock = Mockito.mock(BalanceSegmentRepository.class);
+    Mockito.doReturn(Stream.empty()).when(balanceSegmentRepositoryMock).findByProductIdentifierAndSegmentSetIdentifier(Matchers.anyString(), Matchers.anyString());
+
+    scheduledChargesService = new ScheduledChargesService(chargeDefinitionServiceMock, balanceSegmentRepositoryMock);
+
+    testSubject = new IndividualLoanService(scheduledChargesService);
   }
 
   @Test
@@ -376,7 +385,7 @@
   @Test
   public void getScheduledCharges() {
     final List<ScheduledAction> scheduledActions = ScheduledActionHelpers.getHypotheticalScheduledActions(testCase.initialDisbursementDate, testCase.caseParameters);
-    final List<ScheduledCharge> scheduledCharges = testSubject.getScheduledCharges(testCase.productIdentifier,
+    final List<ScheduledCharge> scheduledCharges = scheduledChargesService.getScheduledCharges(testCase.productIdentifier,
         scheduledActions);
 
     final List<LocalDate> interestCalculationDates = scheduledCharges.stream()
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargesServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargesServiceTest.java
new file mode 100644
index 0000000..032e223
--- /dev/null
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/ScheduledChargesServiceTest.java
@@ -0,0 +1,111 @@
+package io.mifos.individuallending.internal.service;
+
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentEntity;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentRepository;
+import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+@RunWith(Parameterized.class)
+public class ScheduledChargesServiceTest {
+
+  private static final String PRODUCT_IDENTIFIER = "a";
+  private static final String SEGMENT_SET_IDENTIFIER = "b";
+
+  static class TestCase {
+    final String description;
+    List<BalanceSegmentEntity> balanceSegmentEntities = Collections.emptyList();
+    String fromSegment = null;
+    String toSegment = null;
+    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+    Optional<ChargeRange> expectedResult = Optional.empty();
+
+    private TestCase(String description) {
+      this.description = description;
+    }
+
+    TestCase balanceSegmentEntities(List<BalanceSegmentEntity> balanceSegmentEntities) {
+      this.balanceSegmentEntities = balanceSegmentEntities;
+      return this;
+    }
+
+    TestCase fromSegment(String fromSegment) {
+      this.fromSegment = fromSegment;
+      return this;
+    }
+
+    TestCase toSegment(String toSegment) {
+      this.toSegment = toSegment;
+      return this;
+    }
+
+    TestCase expectedResult(@SuppressWarnings("OptionalUsedAsFieldOrParameterType") Optional<ChargeRange> expectedResult) {
+      this.expectedResult = expectedResult;
+      return this;
+    }
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<ScheduledChargesServiceTest.TestCase> ret = new ArrayList<>();
+    ret.add(new ScheduledChargesServiceTest.TestCase("simple"));
+    ret.add(new TestCase("two segments, one referenced")
+        .balanceSegmentEntities(Arrays.asList(
+            new BalanceSegmentEntity(null, SEGMENT_SET_IDENTIFIER, "first", BigDecimal.ZERO),
+            new BalanceSegmentEntity(null, SEGMENT_SET_IDENTIFIER, "second", BigDecimal.TEN)))
+        .fromSegment("first")
+        .toSegment("first")
+        .expectedResult(Optional.of(new ChargeRange(BigDecimal.ZERO, Optional.of(BigDecimal.TEN))))
+    );
+    ret.add(new TestCase("two segments, both referenced")
+        .balanceSegmentEntities(Arrays.asList(
+            new BalanceSegmentEntity(null, SEGMENT_SET_IDENTIFIER, "lower", BigDecimal.ZERO),
+            new BalanceSegmentEntity(null, SEGMENT_SET_IDENTIFIER, "higher", BigDecimal.TEN)))
+        .fromSegment("lower")
+        .toSegment("higher")
+        .expectedResult(Optional.of(new ChargeRange(BigDecimal.ZERO, Optional.empty())))
+    );
+    ret.add(new TestCase("two segments, one mis-referenced")
+        .balanceSegmentEntities(Arrays.asList(
+            new BalanceSegmentEntity(null, SEGMENT_SET_IDENTIFIER, "first", BigDecimal.ZERO),
+            new BalanceSegmentEntity(null, SEGMENT_SET_IDENTIFIER, "second", BigDecimal.TEN)))
+        .fromSegment("first")
+        .toSegment("second2")
+        .expectedResult(Optional.empty())
+    );
+    return ret;
+  }
+
+  private final ScheduledChargesServiceTest.TestCase testCase;
+
+  public ScheduledChargesServiceTest(final ScheduledChargesServiceTest.TestCase testCase) {
+    this.testCase = testCase;
+  }
+
+  @Test
+  public void findChargeRange() throws Exception {
+
+    final ChargeDefinitionService chargeDefinitionServiceMock = Mockito.mock(ChargeDefinitionService.class);
+    final BalanceSegmentRepository balanceSegmentRepositoryMock = Mockito.mock(BalanceSegmentRepository.class);
+
+    Mockito.doReturn(testCase.balanceSegmentEntities.stream())
+        .when(balanceSegmentRepositoryMock)
+        .findByProductIdentifierAndSegmentSetIdentifier(PRODUCT_IDENTIFIER, SEGMENT_SET_IDENTIFIER);
+
+    final ScheduledChargesService testSubject = new ScheduledChargesService(chargeDefinitionServiceMock, balanceSegmentRepositoryMock);
+    final ChargeDefinition chargeDefinition = new ChargeDefinition();
+
+    chargeDefinition.setForSegmentSet(SEGMENT_SET_IDENTIFIER);
+    chargeDefinition.setFromSegment(testCase.fromSegment);
+    chargeDefinition.setToSegment(testCase.toSegment);
+    final Optional<ChargeRange> result = testSubject.findChargeRange(PRODUCT_IDENTIFIER, chargeDefinition);
+    Assert.assertEquals(testCase.expectedResult, result);
+  }
+}
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/portfolio/service/internal/command/handler/ChargeDefinitionCommandHandlerTest.java b/service/src/test/java/io/mifos/portfolio/service/internal/command/handler/ChargeDefinitionCommandHandlerTest.java
index 7b419c0..ecd74d2 100644
--- a/service/src/test/java/io/mifos/portfolio/service/internal/command/handler/ChargeDefinitionCommandHandlerTest.java
+++ b/service/src/test/java/io/mifos/portfolio/service/internal/command/handler/ChargeDefinitionCommandHandlerTest.java
@@ -16,6 +16,7 @@
 package io.mifos.portfolio.service.internal.command.handler;
 
 import io.mifos.portfolio.service.internal.command.DeleteProductChargeDefinitionCommand;
+import io.mifos.portfolio.service.internal.repository.BalanceSegmentRepository;
 import io.mifos.portfolio.service.internal.repository.ChargeDefinitionRepository;
 import io.mifos.core.lang.ServiceException;
 import org.junit.Assert;
@@ -37,7 +38,9 @@
             .when(chargeDefinitionRepositoryMock)
             .findByProductIdAndChargeDefinitionIdentifier(productIdentifier, chargeDefinitionIdentifier);
 
-    final ChargeDefinitionCommandHandler testSubject = new ChargeDefinitionCommandHandler(null, chargeDefinitionRepositoryMock);
+    final BalanceSegmentRepository balanceSegmentRepository = Mockito.mock(BalanceSegmentRepository.class);
+
+    final ChargeDefinitionCommandHandler testSubject = new ChargeDefinitionCommandHandler(null, chargeDefinitionRepositoryMock, balanceSegmentRepository);
 
     try {
       testSubject.process(new DeleteProductChargeDefinitionCommand(productIdentifier, chargeDefinitionIdentifier));