Changed charge persistence so that only those charges which the user may
change are also persisted. This protects users better from breaking their
products than the previous readonly flag did, and also makes data migrations
much easier when changes are made to those charges.  It also makes it possible
to adjust "readonly" charges based on product parameters.

In the process switched from lists to streaming for transporting the charges.
diff --git a/api/src/main/java/io/mifos/portfolio/api/v1/client/ChargeDefinitionIsReadOnly.java b/api/src/main/java/io/mifos/portfolio/api/v1/client/ChargeDefinitionIsReadOnly.java
deleted file mode 100644
index e374850..0000000
--- a/api/src/main/java/io/mifos/portfolio/api/v1/client/ChargeDefinitionIsReadOnly.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * 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.client;
-
-/**
- * @author Myrle Krantz
- */
-public class ChargeDefinitionIsReadOnly extends RuntimeException {
-}
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 7acf0c1..58bba51 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
@@ -49,15 +49,6 @@
   List<Pattern> getAllPatterns();
 
   @RequestMapping(
-      value = "/patterns/{patternpackage}/charges/",
-      method = RequestMethod.GET,
-      produces = MediaType.ALL_VALUE,
-      consumes = MediaType.APPLICATION_JSON_VALUE
-  )
-  List<ChargeDefinition> getAllDefaultChargeDefinitionsForPattern(
-      @PathVariable("patternpackage") final String patternPackage);
-
-  @RequestMapping(
       value = "/products/",
       method = RequestMethod.GET,
       produces = MediaType.ALL_VALUE,
@@ -269,7 +260,6 @@
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
   )
-  @ThrowsException(status = HttpStatus.CONFLICT, exception = ChargeDefinitionIsReadOnly.class)
   void changeChargeDefinition(
       @PathVariable("productidentifier") final String productIdentifier,
       @PathVariable("chargedefinitionidentifier") final String chargeDefinitionIdentifier,
@@ -281,7 +271,6 @@
       produces = MediaType.ALL_VALUE,
       consumes = MediaType.APPLICATION_JSON_VALUE
   )
-  @ThrowsException(status = HttpStatus.CONFLICT, exception = ChargeDefinitionIsReadOnly.class)
   void deleteChargeDefinition(
       @PathVariable("productidentifier") final String productIdentifier,
       @PathVariable("chargedefinitionidentifier") final String chargeDefinitionIdentifier);
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
index a5468af..36e1023 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestChargeDefinitions.java
@@ -18,9 +18,7 @@
 import io.mifos.core.api.util.NotFoundException;
 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.portfolio.api.v1.client.ChargeDefinitionIsReadOnly;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.Product;
 import io.mifos.portfolio.api.v1.events.ChargeDefinitionEvent;
@@ -55,14 +53,6 @@
         .map(ChargeDefinition::getIdentifier)
         .collect(Collectors.toSet());
 
-    final Set<String> expectedReadOnlyChargeDefinitionIdentifiers = Stream.of(
-        //ChargeIdentifiers.ALLOW_FOR_WRITE_OFF_ID,
-        ChargeIdentifiers.DISBURSE_PAYMENT_ID,
-        ChargeIdentifiers.INTEREST_ID,
-        ChargeIdentifiers.REPAY_PRINCIPAL_ID,
-        ChargeIdentifiers.REPAY_INTEREST_ID,
-        ChargeIdentifiers.REPAY_FEES_ID)
-        .collect(Collectors.toSet());
     final Set<String> expectedChangeableChargeDefinitionIdentifiers = Stream.of(
         ChargeIdentifiers.DISBURSEMENT_FEE_ID,
         ChargeIdentifiers.LATE_FEE_ID,
@@ -70,7 +60,7 @@
         ChargeIdentifiers.PROCESSING_FEE_ID)
         .collect(Collectors.toSet());
 
-    Assert.assertEquals(expectedReadOnlyChargeDefinitionIdentifiers, readOnlyChargeDefinitionIdentifiers);
+    Assert.assertTrue(readOnlyChargeDefinitionIdentifiers.isEmpty()); //Not using readonly any more.  Simply not returning charges instead.
     Assert.assertEquals(expectedChangeableChargeDefinitionIdentifiers, changeableChargeDefinitionIdentifiers);
   }
 
@@ -103,7 +93,7 @@
     catch (final NotFoundException ignored) { }
   }
 
-  @Test(expected = ChargeDefinitionIsReadOnly.class)
+  @Test(expected = NotFoundException.class)
   public void shouldNotDeleteReadOnlyChargeDefinition() throws InterruptedException {
     final Product product = createProduct();
 
@@ -173,29 +163,13 @@
   }
 
   @Test
-  public void shouldNotChangeDisbursalChargeDefinition() throws InterruptedException {
+  public void shouldNotGetDisbursalChargeDefinition() throws InterruptedException {
     final Product product = createProduct();
 
-    final ChargeDefinition originalDisbursalChargeDefinition
-        = portfolioManager.getChargeDefinition(product.getIdentifier(), ChargeIdentifiers.DISBURSE_PAYMENT_ID);
-
-    final ChargeDefinition disbursalChargeDefinition
-        = portfolioManager.getChargeDefinition(product.getIdentifier(), ChargeIdentifiers.DISBURSE_PAYMENT_ID);
-    disbursalChargeDefinition.setProportionalTo(ChargeProportionalDesignator.NOT_PROPORTIONAL.getValue());
-    disbursalChargeDefinition.setReadOnly(false);
-
     try {
-      portfolioManager.changeChargeDefinition(
-          product.getIdentifier(),
-          disbursalChargeDefinition.getIdentifier(),
-          disbursalChargeDefinition);
-      Assert.fail("Changing a readonly charge definition should fail.");
+      portfolioManager.getChargeDefinition(product.getIdentifier(), ChargeIdentifiers.DISBURSE_PAYMENT_ID);
+      Assert.fail("Getting a charge derived from configuration should fail.");
     }
-    catch (final ChargeDefinitionIsReadOnly ignore) { }
-
-    final ChargeDefinition chargeDefinitionAsChanged
-        = portfolioManager.getChargeDefinition(product.getIdentifier(), disbursalChargeDefinition.getIdentifier());
-
-    Assert.assertEquals(originalDisbursalChargeDefinition, chargeDefinitionAsChanged);
+    catch (final NotFoundException ignore) { }
   }
 }
diff --git a/component-test/src/main/java/io/mifos/portfolio/TestPatterns.java b/component-test/src/main/java/io/mifos/portfolio/TestPatterns.java
index b46f6e6..a3e83c9 100644
--- a/component-test/src/main/java/io/mifos/portfolio/TestPatterns.java
+++ b/component-test/src/main/java/io/mifos/portfolio/TestPatterns.java
@@ -15,8 +15,6 @@
  */
 package io.mifos.portfolio;
 
-import io.mifos.core.api.util.NotFoundException;
-import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.Pattern;
 import org.junit.Assert;
 import org.junit.Test;
@@ -33,17 +31,4 @@
     Assert.assertNotNull(allPatterns);
     Assert.assertTrue(allPatterns.size() > 0);
   }
-
-  @Test
-  public void shouldReturnDefaultCharges() {
-    final List<ChargeDefinition> chargeDefinitions =
-            portfolioManager.getAllDefaultChargeDefinitionsForPattern("io.mifos.individuallending.api.v1");
-    Assert.assertNotNull(chargeDefinitions);
-    Assert.assertTrue(chargeDefinitions.size() > 0);
-  }
-
-  @Test(expected = NotFoundException.class)
-  public void shouldNotReturnDefaultChargesForNonExistentPackage() {
-    portfolioManager.getAllDefaultChargeDefinitionsForPattern("io.mifos.nonexistentproduct.api.v1");
-  }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
index a1e044d..cf00030 100644
--- a/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
+++ b/service/src/main/java/io/mifos/individuallending/IndividualLendingPatternFactory.java
@@ -21,13 +21,13 @@
 import io.mifos.customer.api.v1.client.CustomerManager;
 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.ChargeProportionalDesignator;
 import io.mifos.individuallending.api.v1.domain.workflow.Action;
 import io.mifos.individuallending.internal.mapper.CaseParametersMapper;
 import io.mifos.individuallending.internal.repository.CaseCreditWorthinessFactorEntity;
 import io.mifos.individuallending.internal.repository.CaseParametersEntity;
 import io.mifos.individuallending.internal.repository.CaseParametersRepository;
 import io.mifos.individuallending.internal.repository.CreditWorthinessFactorType;
+import io.mifos.individuallending.internal.service.ChargeDefinitionService;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
 import io.mifos.individuallending.internal.service.DataContextService;
 import io.mifos.individuallending.internal.service.costcomponent.*;
@@ -44,11 +44,9 @@
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.stream.Collectors;
-
-import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -175,142 +173,8 @@
   }
 
   @Override
-  public List<ChargeDefinition> charges() {
-    final List<ChargeDefinition> ret = defaultIndividualLoanCharges();
-    ret.addAll(requiredIndividualLoanCharges());
-    return ret;
-  }
-
-  public static List<ChargeDefinition> requiredIndividualLoanCharges() {
-    final List<ChargeDefinition> ret = new ArrayList<>();
-
-    final ChargeDefinition disbursePayment = new ChargeDefinition();
-    disbursePayment.setChargeAction(Action.DISBURSE.name());
-    disbursePayment.setIdentifier(DISBURSE_PAYMENT_ID);
-    disbursePayment.setName(DISBURSE_PAYMENT_NAME);
-    disbursePayment.setDescription(DISBURSE_PAYMENT_NAME);
-    disbursePayment.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
-    disbursePayment.setToAccountDesignator(AccountDesignators.ENTRY);
-    disbursePayment.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
-    disbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    disbursePayment.setAmount(BigDecimal.valueOf(100));
-    disbursePayment.setReadOnly(true);
-
-    //TODO: Make multiple write off allowance charges.
-    /*final ChargeDefinition writeOffAllowanceCharge = charge(
-        ALLOW_FOR_WRITE_OFF_NAME,
-        Action.MARK_LATE,
-        BigDecimal.valueOf(30),
-        AccountDesignators.LOAN_FUNDS_SOURCE,
-        AccountDesignators.ARREARS_ALLOWANCE);
-    writeOffAllowanceCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
-    writeOffAllowanceCharge.setReadOnly(true);*/
-
-    final ChargeDefinition interestCharge = charge(
-        INTEREST_NAME,
-        Action.ACCEPT_PAYMENT,
-        BigDecimal.valueOf(100),
-        AccountDesignators.CUSTOMER_LOAN_INTEREST,
-        AccountDesignators.INTEREST_INCOME);
-    interestCharge.setForCycleSizeUnit(ChronoUnit.YEARS);
-    interestCharge.setAccrueAction(Action.APPLY_INTEREST.name());
-    interestCharge.setAccrualAccountDesignator(AccountDesignators.INTEREST_ACCRUAL);
-    interestCharge.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_AND_INTEREST_DESIGNATOR.getValue());
-    interestCharge.setChargeMethod(ChargeDefinition.ChargeMethod.INTEREST);
-    interestCharge.setReadOnly(true);
-
-    final ChargeDefinition customerFeeRepaymentCharge = new ChargeDefinition();
-    customerFeeRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
-    customerFeeRepaymentCharge.setIdentifier(REPAY_FEES_ID);
-    customerFeeRepaymentCharge.setName(REPAY_FEES_NAME);
-    customerFeeRepaymentCharge.setDescription(REPAY_FEES_NAME);
-    customerFeeRepaymentCharge.setFromAccountDesignator(AccountDesignators.ENTRY);
-    customerFeeRepaymentCharge.setToAccountDesignator(AccountDesignators.CUSTOMER_LOAN_FEES);
-    customerFeeRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.TO_ACCOUNT_DESIGNATOR.getValue());
-    customerFeeRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    customerFeeRepaymentCharge.setAmount(BigDecimal.valueOf(100));
-    customerFeeRepaymentCharge.setReadOnly(true);
-
-    final ChargeDefinition customerInterestRepaymentCharge = new ChargeDefinition();
-    customerInterestRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
-    customerInterestRepaymentCharge.setIdentifier(REPAY_INTEREST_ID);
-    customerInterestRepaymentCharge.setName(REPAY_INTEREST_NAME);
-    customerInterestRepaymentCharge.setDescription(REPAY_INTEREST_NAME);
-    customerInterestRepaymentCharge.setFromAccountDesignator(AccountDesignators.ENTRY);
-    customerInterestRepaymentCharge.setToAccountDesignator(AccountDesignators.CUSTOMER_LOAN_INTEREST);
-    customerInterestRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.TO_ACCOUNT_DESIGNATOR.getValue());
-    customerInterestRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    customerInterestRepaymentCharge.setAmount(BigDecimal.valueOf(100));
-    customerInterestRepaymentCharge.setReadOnly(true);
-
-    final ChargeDefinition customerPrincipalRepaymentCharge = new ChargeDefinition();
-    customerPrincipalRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
-    customerPrincipalRepaymentCharge.setIdentifier(REPAY_PRINCIPAL_ID);
-    customerPrincipalRepaymentCharge.setName(REPAY_PRINCIPAL_NAME);
-    customerPrincipalRepaymentCharge.setDescription(REPAY_PRINCIPAL_NAME);
-    customerPrincipalRepaymentCharge.setFromAccountDesignator(AccountDesignators.ENTRY);
-    customerPrincipalRepaymentCharge.setToAccountDesignator(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
-    customerPrincipalRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REQUESTED_REPAYMENT_DESIGNATOR.getValue());
-    customerPrincipalRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    customerPrincipalRepaymentCharge.setAmount(BigDecimal.valueOf(100));
-    customerPrincipalRepaymentCharge.setReadOnly(true);
-
-    ret.add(disbursePayment);
-    //ret.add(writeOffAllowanceCharge);
-    ret.add(interestCharge);
-    ret.add(customerPrincipalRepaymentCharge);
-    ret.add(customerInterestRepaymentCharge);
-    ret.add(customerFeeRepaymentCharge);
-
-    return ret;
-
-  }
-
-  public static List<ChargeDefinition> defaultIndividualLoanCharges() {
-    final List<ChargeDefinition> ret = new ArrayList<>();
-    final ChargeDefinition processingFee = charge(
-        PROCESSING_FEE_NAME,
-        Action.DISBURSE,
-        BigDecimal.ONE,
-        AccountDesignators.CUSTOMER_LOAN_FEES,
-        AccountDesignators.PROCESSING_FEE_INCOME);
-    processingFee.setReadOnly(false);
-
-    final ChargeDefinition loanOriginationFee = charge(
-        LOAN_ORIGINATION_FEE_NAME,
-        Action.DISBURSE,
-        BigDecimal.ONE,
-        AccountDesignators.CUSTOMER_LOAN_FEES,
-        AccountDesignators.ORIGINATION_FEE_INCOME);
-    loanOriginationFee.setReadOnly(false);
-
-    final ChargeDefinition disbursementFee = charge(
-        DISBURSEMENT_FEE_NAME,
-        Action.DISBURSE,
-        BigDecimal.valueOf(0.1),
-        AccountDesignators.CUSTOMER_LOAN_FEES,
-        AccountDesignators.DISBURSEMENT_FEE_INCOME);
-    disbursementFee.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
-    disbursementFee.setReadOnly(false);
-
-    final ChargeDefinition lateFee = charge(
-        LATE_FEE_NAME,
-        Action.ACCEPT_PAYMENT,
-        BigDecimal.TEN,
-        AccountDesignators.CUSTOMER_LOAN_FEES,
-        AccountDesignators.LATE_FEE_INCOME);
-    lateFee.setAccrueAction(Action.MARK_LATE.name());
-    lateFee.setAccrualAccountDesignator(AccountDesignators.LATE_FEE_ACCRUAL);
-    lateFee.setProportionalTo(ChargeProportionalDesignator.CONTRACTUAL_REPAYMENT_DESIGNATOR.getValue());
-    lateFee.setChargeOnTop(true);
-    lateFee.setReadOnly(false);
-
-    ret.add(processingFee);
-    ret.add(loanOriginationFee);
-    ret.add(disbursementFee);
-    ret.add(lateFee);
-
-    return ret;
+  public Stream<ChargeDefinition> defaultConfigurableCharges() {
+    return ChargeDefinitionService.defaultConfigurableIndividualLoanCharges();
   }
 
   @Override
@@ -509,26 +373,4 @@
   public ProductCommandDispatcher getIndividualLendingCommandDispatcher() {
     return this.individualLendingCommandDispatcher;
   }
-
-  private static ChargeDefinition charge(
-          final String name,
-          final Action action,
-          final BigDecimal defaultAmount,
-          final String fromAccount,
-          final String toAccount)
-  {
-    final ChargeDefinition ret = new ChargeDefinition();
-
-    ret.setIdentifier(name.toLowerCase(Locale.US).replace(" ", "-"));
-    ret.setName(name);
-    ret.setDescription(name);
-    ret.setChargeAction(action.name());
-    ret.setAmount(defaultAmount);
-    ret.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
-    ret.setProportionalTo(ChargeProportionalDesignator.MAXIMUM_BALANCE_DESIGNATOR.getValue());
-    ret.setFromAccountDesignator(fromAccount);
-    ret.setToAccountDesignator(toAccount);
-
-    return ret;
-  }
 }
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/ChargeDefinitionService.java b/service/src/main/java/io/mifos/individuallending/internal/service/ChargeDefinitionService.java
new file mode 100644
index 0000000..653496d
--- /dev/null
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/ChargeDefinitionService.java
@@ -0,0 +1,216 @@
+package io.mifos.individuallending.internal.service;
+
+import io.mifos.individuallending.api.v1.domain.product.AccountDesignators;
+import io.mifos.individuallending.api.v1.domain.product.ChargeProportionalDesignator;
+import io.mifos.individuallending.api.v1.domain.workflow.Action;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.service.internal.service.ConfigurableChargeDefinitionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nonnull;
+import java.math.BigDecimal;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static io.mifos.individuallending.api.v1.domain.product.ChargeIdentifiers.*;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ChargeDefinitionService {
+  public static Stream<ChargeDefinition> defaultConfigurableIndividualLoanCharges() {
+    final List<ChargeDefinition> ret = new ArrayList<>();
+    final ChargeDefinition processingFee = charge(
+        PROCESSING_FEE_NAME,
+        Action.DISBURSE,
+        BigDecimal.ONE,
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountDesignators.PROCESSING_FEE_INCOME);
+    processingFee.setReadOnly(false);
+
+    final ChargeDefinition loanOriginationFee = charge(
+        LOAN_ORIGINATION_FEE_NAME,
+        Action.DISBURSE,
+        BigDecimal.ONE,
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountDesignators.ORIGINATION_FEE_INCOME);
+    loanOriginationFee.setReadOnly(false);
+
+    final ChargeDefinition disbursementFee = charge(
+        DISBURSEMENT_FEE_NAME,
+        Action.DISBURSE,
+        BigDecimal.valueOf(0.1),
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountDesignators.DISBURSEMENT_FEE_INCOME);
+    disbursementFee.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
+    disbursementFee.setReadOnly(false);
+
+    final ChargeDefinition lateFee = charge(
+        LATE_FEE_NAME,
+        Action.ACCEPT_PAYMENT,
+        BigDecimal.TEN,
+        AccountDesignators.CUSTOMER_LOAN_FEES,
+        AccountDesignators.LATE_FEE_INCOME);
+    lateFee.setAccrueAction(Action.MARK_LATE.name());
+    lateFee.setAccrualAccountDesignator(AccountDesignators.LATE_FEE_ACCRUAL);
+    lateFee.setProportionalTo(ChargeProportionalDesignator.CONTRACTUAL_REPAYMENT_DESIGNATOR.getValue());
+    lateFee.setChargeOnTop(true);
+    lateFee.setReadOnly(false);
+
+    ret.add(processingFee);
+    ret.add(loanOriginationFee);
+    ret.add(disbursementFee);
+    ret.add(lateFee);
+
+    return ret.stream();
+  }
+
+  public static Stream<ChargeDefinition> individualLoanChargesDerivedFromConfiguration() {
+    final List<ChargeDefinition> ret = new ArrayList<>();
+
+    final ChargeDefinition disbursePayment = new ChargeDefinition();
+    disbursePayment.setChargeAction(Action.DISBURSE.name());
+    disbursePayment.setIdentifier(DISBURSE_PAYMENT_ID);
+    disbursePayment.setName(DISBURSE_PAYMENT_NAME);
+    disbursePayment.setDescription(DISBURSE_PAYMENT_NAME);
+    disbursePayment.setFromAccountDesignator(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+    disbursePayment.setToAccountDesignator(AccountDesignators.ENTRY);
+    disbursePayment.setProportionalTo(ChargeProportionalDesignator.REQUESTED_DISBURSEMENT_DESIGNATOR.getValue());
+    disbursePayment.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    disbursePayment.setAmount(BigDecimal.valueOf(100));
+    disbursePayment.setReadOnly(true);
+
+    //TODO: Make multiple write off allowance defaultCharges.
+    /*final ChargeDefinition writeOffAllowanceCharge = charge(
+        ALLOW_FOR_WRITE_OFF_NAME,
+        Action.MARK_LATE,
+        BigDecimal.valueOf(30),
+        AccountDesignators.LOAN_FUNDS_SOURCE,
+        AccountDesignators.ARREARS_ALLOWANCE);
+    writeOffAllowanceCharge.setProportionalTo(ChargeProportionalDesignator.RUNNING_BALANCE_DESIGNATOR.getValue());
+    writeOffAllowanceCharge.setReadOnly(true);*/
+
+    final ChargeDefinition interestCharge = charge(
+        INTEREST_NAME,
+        Action.ACCEPT_PAYMENT,
+        BigDecimal.valueOf(100),
+        AccountDesignators.CUSTOMER_LOAN_INTEREST,
+        AccountDesignators.INTEREST_INCOME);
+    interestCharge.setForCycleSizeUnit(ChronoUnit.YEARS);
+    interestCharge.setAccrueAction(Action.APPLY_INTEREST.name());
+    interestCharge.setAccrualAccountDesignator(AccountDesignators.INTEREST_ACCRUAL);
+    interestCharge.setProportionalTo(ChargeProportionalDesignator.PRINCIPAL_AND_INTEREST_DESIGNATOR.getValue());
+    interestCharge.setChargeMethod(ChargeDefinition.ChargeMethod.INTEREST);
+    interestCharge.setReadOnly(true);
+
+    final ChargeDefinition customerFeeRepaymentCharge = new ChargeDefinition();
+    customerFeeRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
+    customerFeeRepaymentCharge.setIdentifier(REPAY_FEES_ID);
+    customerFeeRepaymentCharge.setName(REPAY_FEES_NAME);
+    customerFeeRepaymentCharge.setDescription(REPAY_FEES_NAME);
+    customerFeeRepaymentCharge.setFromAccountDesignator(AccountDesignators.ENTRY);
+    customerFeeRepaymentCharge.setToAccountDesignator(AccountDesignators.CUSTOMER_LOAN_FEES);
+    customerFeeRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.TO_ACCOUNT_DESIGNATOR.getValue());
+    customerFeeRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    customerFeeRepaymentCharge.setAmount(BigDecimal.valueOf(100));
+    customerFeeRepaymentCharge.setReadOnly(true);
+
+    final ChargeDefinition customerInterestRepaymentCharge = new ChargeDefinition();
+    customerInterestRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
+    customerInterestRepaymentCharge.setIdentifier(REPAY_INTEREST_ID);
+    customerInterestRepaymentCharge.setName(REPAY_INTEREST_NAME);
+    customerInterestRepaymentCharge.setDescription(REPAY_INTEREST_NAME);
+    customerInterestRepaymentCharge.setFromAccountDesignator(AccountDesignators.ENTRY);
+    customerInterestRepaymentCharge.setToAccountDesignator(AccountDesignators.CUSTOMER_LOAN_INTEREST);
+    customerInterestRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.TO_ACCOUNT_DESIGNATOR.getValue());
+    customerInterestRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    customerInterestRepaymentCharge.setAmount(BigDecimal.valueOf(100));
+    customerInterestRepaymentCharge.setReadOnly(true);
+
+    final ChargeDefinition customerPrincipalRepaymentCharge = new ChargeDefinition();
+    customerPrincipalRepaymentCharge.setChargeAction(Action.ACCEPT_PAYMENT.name());
+    customerPrincipalRepaymentCharge.setIdentifier(REPAY_PRINCIPAL_ID);
+    customerPrincipalRepaymentCharge.setName(REPAY_PRINCIPAL_NAME);
+    customerPrincipalRepaymentCharge.setDescription(REPAY_PRINCIPAL_NAME);
+    customerPrincipalRepaymentCharge.setFromAccountDesignator(AccountDesignators.ENTRY);
+    customerPrincipalRepaymentCharge.setToAccountDesignator(AccountDesignators.CUSTOMER_LOAN_PRINCIPAL);
+    customerPrincipalRepaymentCharge.setProportionalTo(ChargeProportionalDesignator.REQUESTED_REPAYMENT_DESIGNATOR.getValue());
+    customerPrincipalRepaymentCharge.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    customerPrincipalRepaymentCharge.setAmount(BigDecimal.valueOf(100));
+    customerPrincipalRepaymentCharge.setReadOnly(true);
+
+    ret.add(disbursePayment);
+    //ret.add(writeOffAllowanceCharge);
+    ret.add(interestCharge);
+    ret.add(customerPrincipalRepaymentCharge);
+    ret.add(customerInterestRepaymentCharge);
+    ret.add(customerFeeRepaymentCharge);
+
+    return ret.stream();
+  }
+  private final ConfigurableChargeDefinitionService configurableChargeDefinitionService;
+
+  @Autowired
+  public ChargeDefinitionService(
+      final ConfigurableChargeDefinitionService configurableChargeDefinitionService) {
+    this.configurableChargeDefinitionService = configurableChargeDefinitionService;
+  }
+
+  private Stream<ChargeDefinition> getAllChargeDefinitions(final String productIdentifier) {
+    final Stream<ChargeDefinition> configurableChargeDefinitions = configurableChargeDefinitionService.findAllEntities(productIdentifier);
+    final Stream<ChargeDefinition> derivedChargeDefinitions = individualLoanChargesDerivedFromConfiguration();
+    return Stream.concat(configurableChargeDefinitions, derivedChargeDefinitions);
+  }
+
+  @Nonnull
+  public Map<String, List<ChargeDefinition>> getChargeDefinitionsMappedByChargeAction(
+      final String productIdentifier)
+  {
+    final Stream<ChargeDefinition> chargeDefinitions = getAllChargeDefinitions(productIdentifier);
+
+    return chargeDefinitions
+        .collect(Collectors.groupingBy(ChargeDefinition::getChargeAction,
+            Collectors.mapping(x -> x, Collectors.toList())));
+  }
+
+  @Nonnull
+  public Map<String, List<ChargeDefinition>> getChargeDefinitionsMappedByAccrueAction(
+      final String productIdentifier)
+  {
+    final Stream<ChargeDefinition> chargeDefinitions = getAllChargeDefinitions(productIdentifier);
+
+    return chargeDefinitions
+        .filter(x -> x.getAccrueAction() != null)
+        .collect(Collectors.groupingBy(ChargeDefinition::getAccrueAction,
+            Collectors.mapping(x -> x, Collectors.toList())));
+  }
+
+  private static ChargeDefinition charge(
+      final String name,
+      final Action action,
+      final BigDecimal defaultAmount,
+      final String fromAccount,
+      final String toAccount)
+  {
+    final ChargeDefinition ret = new ChargeDefinition();
+
+    ret.setIdentifier(name.toLowerCase(Locale.US).replace(" ", "-"));
+    ret.setName(name);
+    ret.setDescription(name);
+    ret.setChargeAction(action.name());
+    ret.setAmount(defaultAmount);
+    ret.setChargeMethod(ChargeDefinition.ChargeMethod.PROPORTIONAL);
+    ret.setProportionalTo(ChargeProportionalDesignator.MAXIMUM_BALANCE_DESIGNATOR.getValue());
+    ret.setFromAccountDesignator(fromAccount);
+    ret.setToAccountDesignator(toAccount);
+
+    return ret;
+  }
+}
diff --git a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesService.java b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesService.java
index 6749c4e..9ec708f 100644
--- a/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesService.java
+++ b/service/src/main/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesService.java
@@ -15,10 +15,10 @@
  */
 package io.mifos.individuallending.internal.service.schedule;
 
+import io.mifos.individuallending.internal.service.ChargeDefinitionService;
 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;
 
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 c075416..6a284a6 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
@@ -37,9 +37,8 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.List;
 import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -78,7 +77,7 @@
     final ProductEntity productEntity = ProductMapper.map(createProductCommand.getInstance(), false);
     this.productRepository.save(productEntity);
 
-    patternFactory.charges().forEach(charge -> createChargeDefinition(productEntity, charge));
+    patternFactory.defaultConfigurableCharges().forEach(charge -> createChargeDefinition(productEntity, charge));
 
     return createProductCommand.getInstance().getIdentifier();
   }
@@ -139,11 +138,9 @@
     //noinspection PointlessBooleanExpression
     if (changeEnablingOfProductCommand.getEnabled() == true) {
       final Set<AccountAssignment> accountAssignments = ProductMapper.map(productEntity).getAccountAssignments();
-      final List<ChargeDefinition> chargeDefinitions = chargeDefinitionRepository
+      final Stream<ChargeDefinition> chargeDefinitions = chargeDefinitionRepository
               .findByProductId(productEntity.getIdentifier())
-              .stream()
-              .map(ChargeDefinitionMapper::map)
-              .collect(Collectors.toList());
+              .map(ChargeDefinitionMapper::map);
 
       final Set<String> accountAssignmentsRequiredButNotProvided
           = AccountingAdapter.accountAssignmentsRequiredButNotProvided(accountAssignments, chargeDefinitions);
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionRepository.java b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionRepository.java
index 8ff39e9..ededa8b 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionRepository.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/repository/ChargeDefinitionRepository.java
@@ -20,8 +20,8 @@
 import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
-import java.util.List;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -30,7 +30,7 @@
 public interface ChargeDefinitionRepository extends JpaRepository<ChargeDefinitionEntity, Long> {
   @SuppressWarnings("JpaQlInspection")
   @Query("SELECT t FROM ChargeDefinitionEntity t WHERE t.product.identifier = :productIdentifier")
-  List<ChargeDefinitionEntity> findByProductId(@Param("productIdentifier") String productId);
+  Stream<ChargeDefinitionEntity> findByProductId(@Param("productIdentifier") String productId);
 
   @SuppressWarnings("JpaQlInspection")
   @Query("SELECT t FROM ChargeDefinitionEntity t WHERE t.product.identifier = :productIdentifier AND t.identifier = :chargeDefinitionIdentifier")
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/ChargeDefinitionService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/ChargeDefinitionService.java
deleted file mode 100644
index 0230368..0000000
--- a/service/src/main/java/io/mifos/portfolio/service/internal/service/ChargeDefinitionService.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2017 The Mifos Initiative.
- *
- * 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.ChargeDefinition;
-import io.mifos.portfolio.service.internal.mapper.ChargeDefinitionMapper;
-import io.mifos.portfolio.service.internal.repository.ChargeDefinitionRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Nonnull;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-/**
- * @author Myrle Krantz
- */
-@Service
-public class ChargeDefinitionService {
-  private final ChargeDefinitionRepository chargeDefinitionRepository;
-
-  @Autowired
-  public ChargeDefinitionService(final ChargeDefinitionRepository chargeDefinitionRepository) {
-    this.chargeDefinitionRepository = chargeDefinitionRepository;
-  }
-
-  public List<ChargeDefinition> findAllEntities(final String productIdentifier) {
-    return chargeDefinitionRepository.findByProductId(productIdentifier).stream()
-            .map(ChargeDefinitionMapper::map)
-            .collect(Collectors.toList());
-  }
-
-  @Nonnull
-  public Map<String, List<ChargeDefinition>> getChargeDefinitionsMappedByChargeAction(
-          final String productIdentifier)
-  {
-    final List<ChargeDefinition> chargeDefinitions = findAllEntities(productIdentifier);
-
-    return chargeDefinitions.stream()
-            .collect(Collectors.groupingBy(ChargeDefinition::getChargeAction,
-                    Collectors.mapping(x -> x, Collectors.toList())));
-  }
-
-  @Nonnull
-  public Map<String, List<ChargeDefinition>> getChargeDefinitionsMappedByAccrueAction(
-          final String productIdentifier)
-  {
-    final List<ChargeDefinition> chargeDefinitions = findAllEntities(productIdentifier);
-
-    return chargeDefinitions.stream()
-            .filter(x -> x.getAccrueAction() != null)
-            .collect(Collectors.groupingBy(ChargeDefinition::getAccrueAction,
-                    Collectors.mapping(x -> x, Collectors.toList())));
-  }
-
-  public Optional<ChargeDefinition> findByIdentifier(final String productIdentifier, final String identifier) {
-    return chargeDefinitionRepository
-            .findByProductIdAndChargeDefinitionIdentifier(productIdentifier, identifier)
-            .map(ChargeDefinitionMapper::map);
-  }
-}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/ConfigurableChargeDefinitionService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/ConfigurableChargeDefinitionService.java
new file mode 100644
index 0000000..902e06d
--- /dev/null
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/ConfigurableChargeDefinitionService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 The Mifos Initiative.
+ *
+ * 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.ChargeDefinition;
+import io.mifos.portfolio.service.internal.mapper.ChargeDefinitionMapper;
+import io.mifos.portfolio.service.internal.repository.ChargeDefinitionRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ConfigurableChargeDefinitionService {
+  private final ChargeDefinitionRepository chargeDefinitionRepository;
+
+  @Autowired
+  public ConfigurableChargeDefinitionService(final ChargeDefinitionRepository chargeDefinitionRepository) {
+    this.chargeDefinitionRepository = chargeDefinitionRepository;
+  }
+
+  public Stream<ChargeDefinition> findAllEntities(final String productIdentifier) {
+    return chargeDefinitionRepository.findByProductId(productIdentifier)
+            .map(ChargeDefinitionMapper::map);
+  }
+
+  public Optional<ChargeDefinition> findByIdentifier(final String productIdentifier, final String identifier) {
+    return chargeDefinitionRepository
+            .findByProductIdAndChargeDefinitionIdentifier(productIdentifier, identifier)
+            .map(ChargeDefinitionMapper::map);
+  }
+}
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/PatternService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/PatternService.java
index 2d40f29..457d529 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/service/PatternService.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/PatternService.java
@@ -15,11 +15,9 @@
  */
 package io.mifos.portfolio.service.internal.service;
 
-import io.mifos.core.lang.ServiceException;
-import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.Pattern;
-import io.mifos.products.spi.PatternFactory;
 import io.mifos.portfolio.service.internal.pattern.PatternFactoryRegistry;
+import io.mifos.products.spi.PatternFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -50,10 +48,4 @@
   {
     return patternFactoryRegistry.getPatternFactoryForPackage(identifier).map(PatternFactory::pattern);
   }
-
-  public List<ChargeDefinition> findDefaultChargeDefinitions(final String patternPackage) {
-    return patternFactoryRegistry.getPatternFactoryForPackage(patternPackage)
-            .orElseThrow(() -> ServiceException.notFound("Pattern with package " + patternPackage + " doesn't exist."))
-            .charges();
-  }
 }
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/service/ProductService.java b/service/src/main/java/io/mifos/portfolio/service/internal/service/ProductService.java
index 1bc10d4..4942da1 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/service/ProductService.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/service/ProductService.java
@@ -34,6 +34,7 @@
 import javax.annotation.Nullable;
 import java.util.*;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
@@ -42,16 +43,16 @@
 public class ProductService {
 
   private final ProductRepository productRepository;
-  private final ChargeDefinitionService chargeDefinitionService;
+  private final ConfigurableChargeDefinitionService configurableChargeDefinitionService;
   private final AccountingAdapter accountingAdapter;
 
   @Autowired
   public ProductService(final ProductRepository productRepository,
-                        final ChargeDefinitionService chargeDefinitionService,
+                        final ConfigurableChargeDefinitionService configurableChargeDefinitionService,
                         final AccountingAdapter accountingAdapter) {
     super();
     this.productRepository = productRepository;
-    this.chargeDefinitionService = chargeDefinitionService;
+    this.configurableChargeDefinitionService = configurableChargeDefinitionService;
     this.accountingAdapter = accountingAdapter;
   }
 
@@ -120,12 +121,12 @@
       return false;
     final Product product = maybeProduct.get();
     final Set<AccountAssignment> accountAssignments = product.getAccountAssignments();
-    final List<ChargeDefinition> chargeDefinitions = chargeDefinitionService.findAllEntities(identifier);
+    final Stream<ChargeDefinition> chargeDefinitions = configurableChargeDefinitionService.findAllEntities(identifier);
     return AccountingAdapter.accountAssignmentsRequiredButNotProvided(accountAssignments, chargeDefinitions).isEmpty();
   }
 
   public Set<AccountAssignment> getIncompleteAccountAssignments(final String identifier) {
-    final Set<String> requiredAccountDesignators = AccountingAdapter.getRequiredAccountDesignators(chargeDefinitionService.findAllEntities(identifier));
+    final Set<String> requiredAccountDesignators = AccountingAdapter.getRequiredAccountDesignators(configurableChargeDefinitionService.findAllEntities(identifier));
 
     final AccountAssignmentValidator accountAssignmentValidator
             = new AccountAssignmentValidator(findByIdentifier(identifier)
diff --git a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
index 0c427d5..b2a2067 100644
--- a/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
+++ b/service/src/main/java/io/mifos/portfolio/service/internal/util/AccountingAdapter.java
@@ -252,7 +252,7 @@
 
   public static Set<String> accountAssignmentsRequiredButNotProvided(
           final Set<AccountAssignment> accountAssignments,
-          final List<ChargeDefinition> chargeDefinitionEntities) {
+          final Stream<ChargeDefinition> chargeDefinitionEntities) {
     final Set<String> allAccountDesignatorsRequired = getRequiredAccountDesignators(chargeDefinitionEntities);
     final Set<String> allAccountDesignatorsDefined = accountAssignments.stream().map(AccountAssignment::getDesignator)
             .collect(Collectors.toSet());
@@ -264,8 +264,8 @@
     }
   }
 
-  public static Set<String> getRequiredAccountDesignators(final Collection<ChargeDefinition> chargeDefinitionEntities) {
-    return chargeDefinitionEntities.stream()
+  public static Set<String> getRequiredAccountDesignators(final Stream<ChargeDefinition> chargeDefinitionEntities) {
+    return chargeDefinitionEntities
             .flatMap(AccountingAdapter::getAutomaticActionAccountDesignators)
             .filter(Objects::nonNull)
             .collect(Collectors.toSet());
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/ChargeDefinitionRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/ChargeDefinitionRestController.java
index df91bb3..d77dc4d 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/ChargeDefinitionRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/ChargeDefinitionRestController.java
@@ -23,7 +23,7 @@
 import io.mifos.portfolio.service.internal.command.ChangeChargeDefinitionCommand;
 import io.mifos.portfolio.service.internal.command.CreateChargeDefinitionCommand;
 import io.mifos.portfolio.service.internal.command.DeleteProductChargeDefinitionCommand;
-import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
+import io.mifos.portfolio.service.internal.service.ConfigurableChargeDefinitionService;
 import io.mifos.portfolio.service.internal.service.ProductService;
 import io.mifos.core.command.gateway.CommandGateway;
 import io.mifos.core.lang.ServiceException;
@@ -35,6 +35,7 @@
 
 import javax.validation.Valid;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * @author Myrle Krantz
@@ -44,15 +45,15 @@
 @RequestMapping("/products/{productidentifier}/charges/")
 public class ChargeDefinitionRestController {
   private final CommandGateway commandGateway;
-  private final ChargeDefinitionService chargeDefinitionService;
+  private final ConfigurableChargeDefinitionService configurableChargeDefinitionService;
   private final ProductService productService;
 
   @Autowired
   public ChargeDefinitionRestController(
-          final CommandGateway commandGateway,
-          final ChargeDefinitionService chargeDefinitionService, final ProductService productService) {
+      final CommandGateway commandGateway,
+      final ConfigurableChargeDefinitionService configurableChargeDefinitionService, final ProductService productService) {
     this.commandGateway = commandGateway;
-    this.chargeDefinitionService = chargeDefinitionService;
+    this.configurableChargeDefinitionService = configurableChargeDefinitionService;
     this.productService = productService;
   }
 
@@ -68,7 +69,8 @@
   {
     checkProductExists(productIdentifier);
 
-    return chargeDefinitionService.findAllEntities(productIdentifier);
+    return configurableChargeDefinitionService.findAllEntities(productIdentifier)
+        .collect(Collectors.toList());
   }
 
   @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
@@ -87,7 +89,7 @@
     if (instance.isReadOnly())
       throw ServiceException.badRequest("Created charges cannot be read only.");
 
-    chargeDefinitionService.findByIdentifier(productIdentifier, instance.getIdentifier())
+    configurableChargeDefinitionService.findByIdentifier(productIdentifier, instance.getIdentifier())
         .ifPresent(taskDefinition -> {throw ServiceException.conflict("Duplicate identifier: " + taskDefinition.getIdentifier());});
 
     this.commandGateway.process(new CreateChargeDefinitionCommand(productIdentifier, instance));
@@ -107,7 +109,7 @@
   {
     checkProductExists(productIdentifier);
 
-    return chargeDefinitionService.findByIdentifier(productIdentifier, chargeDefinitionIdentifier).orElseThrow(
+    return configurableChargeDefinitionService.findByIdentifier(productIdentifier, chargeDefinitionIdentifier).orElseThrow(
         () -> ServiceException.notFound("No charge definition with the identifier '" + chargeDefinitionIdentifier  + "' found."));
   }
 
@@ -123,7 +125,7 @@
           @PathVariable("chargedefinitionidentifier") final String chargeDefinitionIdentifier,
           @RequestBody @Valid final ChargeDefinition instance)
   {
-    checkChargeExistsInProductAndIsNotReadOnly(productIdentifier, chargeDefinitionIdentifier);
+    checkChargeExistsInProduct(productIdentifier, chargeDefinitionIdentifier);
 
     if (instance.isReadOnly())
       throw ServiceException.badRequest("Created charges cannot be read only.");
@@ -147,23 +149,18 @@
           @PathVariable("productidentifier") final String productIdentifier,
           @PathVariable("chargedefinitionidentifier") final String chargeDefinitionIdentifier)
   {
-    checkChargeExistsInProductAndIsNotReadOnly(productIdentifier, chargeDefinitionIdentifier);
+    checkChargeExistsInProduct(productIdentifier, chargeDefinitionIdentifier);
 
     commandGateway.process(new DeleteProductChargeDefinitionCommand(productIdentifier, chargeDefinitionIdentifier));
 
     return ResponseEntity.accepted().build();
   }
 
-  private void checkChargeExistsInProductAndIsNotReadOnly(final String productIdentifier,
-                                                          final String chargeDefinitionIdentifier) {
-    final boolean readOnly = chargeDefinitionService.findByIdentifier(productIdentifier, chargeDefinitionIdentifier)
+  private void checkChargeExistsInProduct(final String productIdentifier,
+                                          final String chargeDefinitionIdentifier) {
+    configurableChargeDefinitionService.findByIdentifier(productIdentifier, chargeDefinitionIdentifier)
         .orElseThrow(() -> ServiceException.notFound("No charge definition ''{0}.{1}'' found.",
-            productIdentifier, chargeDefinitionIdentifier))
-        .isReadOnly();
-
-    if (readOnly)
-      throw ServiceException.conflict("Charge definition is read only ''{0}.{1}''",
-          productIdentifier, chargeDefinitionIdentifier);
+            productIdentifier, chargeDefinitionIdentifier));
   }
 
   private void checkProductExists(final String productIdentifier) {
diff --git a/service/src/main/java/io/mifos/portfolio/service/rest/PatternRestController.java b/service/src/main/java/io/mifos/portfolio/service/rest/PatternRestController.java
index 408453b..cf10ec5 100644
--- a/service/src/main/java/io/mifos/portfolio/service/rest/PatternRestController.java
+++ b/service/src/main/java/io/mifos/portfolio/service/rest/PatternRestController.java
@@ -17,14 +17,15 @@
 
 import io.mifos.anubis.annotation.AcceptedTokenType;
 import io.mifos.anubis.annotation.Permittable;
-import io.mifos.core.lang.ServiceException;
 import io.mifos.portfolio.api.v1.PermittableGroupIds;
-import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
 import io.mifos.portfolio.api.v1.domain.Pattern;
 import io.mifos.portfolio.service.internal.service.PatternService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
 
@@ -53,19 +54,4 @@
   List<Pattern> getAllPatterns() {
     return this.patternService.findAllEntities();
   }
-
-  @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.PRODUCT_MANAGEMENT)
-  @RequestMapping(
-          value = "/{patternpackage}/charges/",
-          method = RequestMethod.GET,
-          consumes = MediaType.ALL_VALUE,
-          produces = MediaType.APPLICATION_JSON_VALUE
-  )
-  public
-  @ResponseBody
-  List<ChargeDefinition> getAllDefaultChargeDefinitionsForPattern(@PathVariable("patternpackage") final String patternPackage) {
-    final Pattern pattern = this.patternService.findByIdentifier(patternPackage)
-            .orElseThrow(() -> ServiceException.notFound("Pattern with package " + patternPackage + " doesn't exist."));
-    return this.patternService.findDefaultChargeDefinitions(patternPackage);
-  }
 }
diff --git a/service/src/main/java/io/mifos/products/spi/PatternFactory.java b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
index 9c153be..6802e3f 100644
--- a/service/src/main/java/io/mifos/products/spi/PatternFactory.java
+++ b/service/src/main/java/io/mifos/products/spi/PatternFactory.java
@@ -16,20 +16,23 @@
 package io.mifos.products.spi;
 
 
-import io.mifos.portfolio.api.v1.domain.*;
+import io.mifos.portfolio.api.v1.domain.Case;
+import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
+import io.mifos.portfolio.api.v1.domain.Pattern;
+import io.mifos.portfolio.api.v1.domain.Payment;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
-import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
  */
 public interface PatternFactory {
   Pattern pattern();
-  List<ChargeDefinition> charges();
+  Stream<ChargeDefinition> defaultConfigurableCharges();
   void checkParameters(String parameters);
   void persistParameters(Long caseId, String parameters);
   void changeParameters(Long caseId, String parameters);
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/DefaultChargeDefinitionsMocker.java b/service/src/test/java/io/mifos/individuallending/internal/service/DefaultChargeDefinitionsMocker.java
index 2c850e6..4ef0f05 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/DefaultChargeDefinitionsMocker.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/DefaultChargeDefinitionsMocker.java
@@ -1,19 +1,17 @@
 package io.mifos.individuallending.internal.service;
 
-import io.mifos.individuallending.IndividualLendingPatternFactory;
 import io.mifos.portfolio.api.v1.domain.ChargeDefinition;
-import io.mifos.portfolio.service.internal.service.ChargeDefinitionService;
 import org.mockito.Mockito;
 
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public class DefaultChargeDefinitionsMocker {
-  private static List<ChargeDefinition> charges() {
-    final List<ChargeDefinition> ret = IndividualLendingPatternFactory.requiredIndividualLoanCharges();
-    ret.addAll(IndividualLendingPatternFactory.defaultIndividualLoanCharges());
-    return ret;
+  private static Stream<ChargeDefinition> charges() {
+    return Stream.concat(ChargeDefinitionService.defaultConfigurableIndividualLoanCharges(),
+        ChargeDefinitionService.individualLoanChargesDerivedFromConfiguration());
   }
 
   public static ChargeDefinitionService getChargeDefinitionService(final List<ChargeDefinition> changedCharges) {
@@ -21,11 +19,11 @@
         .collect(Collectors.toMap(ChargeDefinition::getIdentifier, x -> x));
 
     final List<ChargeDefinition> defaultChargesWithFeesReplaced =
-        charges().stream().map(x -> changedChargesMap.getOrDefault(x.getIdentifier(), x))
+        charges().map(x -> changedChargesMap.getOrDefault(x.getIdentifier(), x))
             .collect(Collectors.toList());
 
 
-    final ChargeDefinitionService chargeDefinitionServiceMock = Mockito.mock(ChargeDefinitionService.class);
+    final ChargeDefinitionService configurableChargeDefinitionServiceMock = Mockito.mock(ChargeDefinitionService.class);
     final Map<String, List<ChargeDefinition>> chargeDefinitionsByChargeAction = defaultChargesWithFeesReplaced.stream()
         .collect(Collectors.groupingBy(ChargeDefinition::getChargeAction,
             Collectors.mapping(x -> x, Collectors.toList())));
@@ -33,9 +31,9 @@
         .filter(x -> x.getAccrueAction() != null)
         .collect(Collectors.groupingBy(ChargeDefinition::getAccrueAction,
             Collectors.mapping(x -> x, Collectors.toList())));
-    Mockito.doReturn(chargeDefinitionsByChargeAction).when(chargeDefinitionServiceMock).getChargeDefinitionsMappedByChargeAction(Mockito.any());
-    Mockito.doReturn(chargeDefinitionsByAccrueAction).when(chargeDefinitionServiceMock).getChargeDefinitionsMappedByAccrueAction(Mockito.any());
+    Mockito.doReturn(chargeDefinitionsByChargeAction).when(configurableChargeDefinitionServiceMock).getChargeDefinitionsMappedByChargeAction(Mockito.any());
+    Mockito.doReturn(chargeDefinitionsByAccrueAction).when(configurableChargeDefinitionServiceMock).getChargeDefinitionsMappedByAccrueAction(Mockito.any());
 
-    return chargeDefinitionServiceMock;
+    return configurableChargeDefinitionServiceMock;
   }
 }
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderServiceTestHarness.java b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderServiceTestHarness.java
index a59d2ce..20002b3 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderServiceTestHarness.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/costcomponent/PaymentBuilderServiceTestHarness.java
@@ -1,13 +1,13 @@
 package io.mifos.individuallending.internal.service.costcomponent;
 
 import io.mifos.individuallending.internal.repository.CaseParametersEntity;
+import io.mifos.individuallending.internal.service.ChargeDefinitionService;
 import io.mifos.individuallending.internal.service.DataContextOfAction;
 import io.mifos.individuallending.internal.service.DefaultChargeDefinitionsMocker;
 import io.mifos.individuallending.internal.service.schedule.ScheduledChargesService;
 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;
 import org.mockito.Mockito;
 
 import java.time.temporal.ChronoUnit;
diff --git a/service/src/test/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesServiceTest.java b/service/src/test/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesServiceTest.java
index 352d034..ad1f95c 100644
--- a/service/src/test/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesServiceTest.java
+++ b/service/src/test/java/io/mifos/individuallending/internal/service/schedule/ScheduledChargesServiceTest.java
@@ -15,10 +15,10 @@
  */
 package io.mifos.individuallending.internal.service.schedule;
 
+import io.mifos.individuallending.internal.service.ChargeDefinitionService;
 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;