fineract-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From vorbur...@apache.org
Subject [fineract] 01/07: A new rates module was added to define new rates that can be used to set min and max nominal interest rate, when a new loan account is created they can be used to determine which rates may be applicable for the loan account.
Date Fri, 13 Mar 2020 16:15:40 GMT
This is an automated email from the ASF dual-hosted git repository.

vorburger pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit a7d4c83e9258ca1865d42750dd4c5ad496a05f44
Author: Angel Cajas <angel.cajas@bowpi.com>
AuthorDate: Mon Jul 30 16:52:17 2018 -0600

    A new rates module was added to define new rates that can be used to set min and max nominal interest rate, when a new loan account is created they can be used to determine which rates may be applicable for the loan account.
---
 .../commands/service/CommandWrapperBuilder.java    |  17 ++
 .../domain/ConfigurationDomainService.java         |   2 +
 .../domain/ConfigurationDomainServiceJpa.java      |  11 ++
 .../api/LoanTransactionsApiResource.java           |   6 +-
 .../loanaccount/api/LoansApiResource.java          |  63 ++++---
 .../loanaccount/data/LoanAccountData.java          |  71 ++++++--
 .../loanaccount/data/LoanChargePaidByData.java     |  15 ++
 .../loanaccount/data/LoanTransactionData.java      |  10 +
 .../portfolio/loanaccount/domain/Loan.java         |  38 +++-
 ...alculateLoanScheduleQueryFromApiJsonHelper.java |   3 +-
 .../LoanApplicationCommandFromApiJsonHelper.java   |   2 +-
 ...ationWritePlatformServiceJpaRepositoryImpl.java |  13 +-
 .../loanaccount/service/LoanAssembler.java         |  18 +-
 .../LoanChargePaidByReadPlatformService.java       |  27 +++
 .../LoanChargePaidByReadPlatformServiceImpl.java   |  82 +++++++++
 .../loanproduct/LoanProductConstants.java          |   1 +
 .../loanproduct/api/LoanProductsApiResource.java   |  24 ++-
 .../loanproduct/data/LoanProductData.java          |  43 ++++-
 .../portfolio/loanproduct/domain/LoanProduct.java  |  51 +++++-
 .../domain/LoanProductMinMaxConstraints.java       |   8 +
 .../domain/LoanProductRelatedDetail.java           |   3 +
 .../serialization/LoanProductDataValidator.java    |   3 +-
 .../LoanProductReadPlatformServiceImpl.java        |  22 ++-
 ...oductWritePlatformServiceJpaRepositoryImpl.java |  40 +++-
 .../portfolio/rate/api/RateApiConstants.java       |  29 +++
 .../portfolio/rate/api/RateApiResource.java        | 137 ++++++++++++++
 .../fineract/portfolio/rate/data/RateData.java     |  53 ++++++
 .../fineract/portfolio/rate/domain/Rate.java       | 201 +++++++++++++++++++++
 .../portfolio/rate/domain/RateRepository.java      |  36 ++++
 .../rate/domain/RateRepositoryWrapper.java         |  70 +++++++
 .../rate/exception/RateAlreadyExistException.java  |  30 +++
 .../rate/exception/RateNotFoundException.java      |  35 ++++
 .../rate/handler/CreateRateCommandHandler.java     |  48 +++++
 .../rate/handler/UpdateRateCommandHandler.java     |  51 ++++++
 ...teDefinitionCommandFromApiJsonDeserializer.java | 129 +++++++++++++
 .../portfolio/rate/service/RateAssembler.java      |  80 ++++++++
 .../portfolio/rate/service/RateReadService.java    |  44 +++++
 .../rate/service/RateReadServiceImpl.java          | 150 +++++++++++++++
 .../portfolio/rate/service/RateWriteService.java   |  34 ++++
 .../rate/service/RateWriteServiceImpl.java         | 157 ++++++++++++++++
 .../sql/migrations/core_db/V352__rates.sql         |  67 +++++++
 41 files changed, 1841 insertions(+), 83 deletions(-)

diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 72bb97a..f713c1b 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -3147,4 +3147,21 @@ public class CommandWrapperBuilder {
         this.href = "/self/pocket?command="+PocketApiConstants.delinkAccountsFromPocketCommandParam;
         return this;
     }
+
+    public CommandWrapperBuilder createRate() {
+        this.actionName = "CREATE";
+        this.entityName = "RATE";
+        this.entityId = null;
+        this.href = "/rates/template";
+        return this;
+    }
+
+    public CommandWrapperBuilder updateRate(final Long rateId) {
+        this.actionName = "UPDATE";
+        this.entityName = "RATE";
+        this.entityId = rateId;
+        this.href = "/rates/" + rateId;
+        return this;
+    }
+
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
index 13f83de..7c66561 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
@@ -92,4 +92,6 @@ public interface ConfigurationDomainService {
     Integer retrieveOTPCharacterLength();
 
     Integer retrieveOTPLiveTime();
+
+    boolean isSubRatesEnabled();
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
index b534a1b..54a955f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -332,4 +332,15 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
         }
         return configurations.get(key);
     }
+
+    @Override
+    public boolean isSubRatesEnabled() {
+        GlobalConfigurationPropertyData configuration = getGlobalConfigurationPropertyData("sub-rates");
+        if (configuration==null){
+            return false;
+        }else{
+            return configuration.isEnabled();
+        }
+    }
+
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 187fdf1..fd88a67 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -54,6 +54,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
 import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService;
 import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
 import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
@@ -82,19 +83,21 @@ public class LoanTransactionsApiResource {
     private final DefaultToApiJsonSerializer<LoanTransactionData> toApiJsonSerializer;
     private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
     private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+    private final LoanChargePaidByReadPlatformService loanChargePaidByReadPlatformService;
 
     @Autowired
     public LoanTransactionsApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
             final ApiRequestParameterHelper apiRequestParameterHelper,
             final DefaultToApiJsonSerializer<LoanTransactionData> toApiJsonSerializer,
             final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
-            PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
+            PaymentTypeReadPlatformService paymentTypeReadPlatformService, LoanChargePaidByReadPlatformService loanChargePaidByReadPlatformService) {
         this.context = context;
         this.loanReadPlatformService = loanReadPlatformService;
         this.apiRequestParameterHelper = apiRequestParameterHelper;
         this.toApiJsonSerializer = toApiJsonSerializer;
         this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
         this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
+        this.loanChargePaidByReadPlatformService = loanChargePaidByReadPlatformService;
     }
 
     private boolean is(final String commandParam, final String commandValue) {
@@ -170,6 +173,7 @@ public class LoanTransactionsApiResource {
         this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
 
         LoanTransactionData transactionData = this.loanReadPlatformService.retrieveLoanTransaction(loanId, transactionId);
+        transactionData.setLoanChargePaidByList(this.loanChargePaidByReadPlatformService.getLoanChargesPaidByTransactionId(transactionId));
         final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
         if (settings.isTemplate()) {
             final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
index 8e6a77f..7b65d8a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
@@ -64,6 +64,7 @@ import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookP
 import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService;
 import org.apache.fineract.infrastructure.codes.data.CodeValueData;
 import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
 import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
 import org.apache.fineract.infrastructure.core.api.JsonQuery;
@@ -131,6 +132,8 @@ import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatform
 import org.apache.fineract.portfolio.note.data.NoteData;
 import org.apache.fineract.portfolio.note.domain.NoteType;
 import org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl;
+import org.apache.fineract.portfolio.rate.data.RateData;
+import org.apache.fineract.portfolio.rate.service.RateReadService;
 import org.apache.fineract.portfolio.savings.DepositAccountType;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -259,7 +262,7 @@ public class LoansApiResource {
             "syncDisbursementWithMeeting", "loanCounter", "loanProductCounter", "notes", "accountLinkingOptions", "linkedAccount",
             "interestRateDifferential", "isFloatingInterestRate", "interestRatesPeriods", LoanApiConstants.canUseForTopup,
             LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.topupAmount, LoanApiConstants.clientActiveLoanOptions,
-            LoanApiConstants.datatables));
+            LoanApiConstants.datatables, LoanProductConstants.ratesParamName));
 
     private final Set<String> LOAN_APPROVAL_DATA_PARAMETERS = new HashSet<>(Arrays.asList("approvalDate", "approvalAmount"));
     private final String resourceNameForPermissions = "LOAN";
@@ -291,30 +294,32 @@ public class LoansApiResource {
     private final EntityDatatableChecksReadService entityDatatableChecksReadService;
     private final BulkImportWorkbookService bulkImportWorkbookService;
     private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
-
+    private final RateReadService rateReadService;
+    private final ConfigurationDomainService configurationDomainService;
 
     @Autowired
     public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
-            final LoanProductReadPlatformService loanProductReadPlatformService,
-            final LoanDropdownReadPlatformService dropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService,
-            final ChargeReadPlatformService chargeReadPlatformService, final LoanChargeReadPlatformService loanChargeReadPlatformService,
-            final CollateralReadPlatformService loanCollateralReadPlatformService,
-            final LoanScheduleCalculationPlatformService calculationPlatformService,
-            final GuarantorReadPlatformService guarantorReadPlatformService,
-            final CodeValueReadPlatformService codeValueReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
-            final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer,
-            final DefaultToApiJsonSerializer<LoanApprovalData> loanApprovalDataToApiJsonSerializer,
-            final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer,
-            final ApiRequestParameterHelper apiRequestParameterHelper, final FromJsonHelper fromJsonHelper,
-            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
-            final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformServiceImpl noteReadPlatformService,
-            final PortfolioAccountReadPlatformService portfolioAccountReadPlatformServiceImpl,
-            final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
-            final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService,
-            final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
-            final EntityDatatableChecksReadService entityDatatableChecksReadService,
-            final BulkImportWorkbookService bulkImportWorkbookService,
-            final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService) {
+        final LoanProductReadPlatformService loanProductReadPlatformService,
+        final LoanDropdownReadPlatformService dropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService,
+        final ChargeReadPlatformService chargeReadPlatformService, final LoanChargeReadPlatformService loanChargeReadPlatformService,
+        final CollateralReadPlatformService loanCollateralReadPlatformService,
+        final LoanScheduleCalculationPlatformService calculationPlatformService,
+        final GuarantorReadPlatformService guarantorReadPlatformService,
+        final CodeValueReadPlatformService codeValueReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
+        final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer,
+        final DefaultToApiJsonSerializer<LoanApprovalData> loanApprovalDataToApiJsonSerializer,
+        final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer,
+        final ApiRequestParameterHelper apiRequestParameterHelper, final FromJsonHelper fromJsonHelper,
+        final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+        final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformServiceImpl noteReadPlatformService,
+        final PortfolioAccountReadPlatformService portfolioAccountReadPlatformServiceImpl,
+        final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
+        final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService,
+        final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
+        final EntityDatatableChecksReadService entityDatatableChecksReadService,
+        final BulkImportWorkbookService bulkImportWorkbookService,
+        final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService, final RateReadService rateReadService, 
+        final ConfigurationDomainService configurationDomainService) {
         this.context = context;
         this.loanReadPlatformService = loanReadPlatformService;
         this.loanProductReadPlatformService = loanProductReadPlatformService;
@@ -340,8 +345,10 @@ public class LoansApiResource {
         this.loanScheduleHistoryReadPlatformService = loanScheduleHistoryReadPlatformService;
         this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
         this.entityDatatableChecksReadService = entityDatatableChecksReadService;
+        this.rateReadService = rateReadService;
         this.bulkImportWorkbookService=bulkImportWorkbookService;
         this.bulkImportWorkbookPopulatorService=bulkImportWorkbookPopulatorService;
+        this.configurationDomainService = configurationDomainService;
     }
 
     /*
@@ -397,6 +404,7 @@ public class LoansApiResource {
         LoanAccountData newLoanAccount = null;
         Long officeId = null;
         Collection<PortfolioAccountData> accountLinkingOptions = null;
+        boolean isRatesEnabled = this.configurationDomainService.isSubRatesEnabled();
 
         if (productId != null) {
             newLoanAccount = this.loanReadPlatformService.retrieveLoanProductDetailsTemplate(productId, clientId, groupId);
@@ -474,7 +482,7 @@ public class LoansApiResource {
             // add product options, allowed loan officers and calendar options
             // (calendar options will be null in individual loan)
             newLoanAccount = LoanAccountData.associationsAndTemplate(newLoanAccount, productOptions, allowedLoanOfficers, calendarOptions,
-                    accountLinkingOptions);
+                    accountLinkingOptions, isRatesEnabled);
         }
         final List<DatatableData> datatableTemplates = this.entityDatatableChecksReadService
                 .retrieveTemplates(StatusEnum.CREATE.getCode().longValue(), EntityTables.LOAN.getName(), productId);
@@ -736,13 +744,20 @@ public class LoansApiResource {
 
         paidInAdvanceTemplate = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId);
 
+        //Get rates from Loan
+        boolean isRatesEnabled = this.configurationDomainService.isSubRatesEnabled();
+        List<RateData> rates = null;
+        if (isRatesEnabled){
+          rates = this.rateReadService.retrieveLoanRates(loanId);
+        }
+
         final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments,
                 charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
                 repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, repaymentStrategyOptions,
                 interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
                 fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions, loanCollateralOptions,
                 calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
-                overdueCharges, paidInAdvanceTemplate, interestRatesPeriods, clientActiveLoanOptions);
+                overdueCharges, paidInAdvanceTemplate, interestRatesPeriods, clientActiveLoanOptions, rates, isRatesEnabled);
 
         final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
                 mandatoryResponseParameters);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
index d5291ff..052efe0 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -50,6 +50,7 @@ import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrat
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
 import org.apache.fineract.portfolio.note.data.NoteData;
+import org.apache.fineract.portfolio.rate.data.RateData;
 import org.joda.time.LocalDate;
 import org.springframework.util.CollectionUtils;
 
@@ -219,6 +220,10 @@ public class LoanAccountData {
     private List<DatatableData> datatables = null;
     private final Boolean isEqualAmortization;
 
+    //Rate
+    private final List<RateData> rates;
+    private final Boolean isRatesEnabled;
+
     //import fields
     private String dateFormat;
     private String locale;
@@ -311,6 +316,7 @@ public class LoanAccountData {
         this.groupId=groupId;
         this.expectedDisbursementDate=expectedDisbursementDate;
         this.charges = charges;
+        this.rates=null;
         this.id = null;
         this.accountNo = null;
 
@@ -418,6 +424,7 @@ public class LoanAccountData {
         this.minimumGap = null;
         this.maximumGap = null;
         this.isEqualAmortization = null;
+        this.isRatesEnabled = false;
     }
 
 
@@ -580,6 +587,9 @@ public class LoanAccountData {
         final String closureLoanAccountNo = null;
         final BigDecimal topupAmount = null;
         final boolean isEqualAmortization = false;
+        final List<RateData> rates = null;
+        final Boolean isRatesEnabled = false;
+
         return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
                 loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
                 loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
@@ -598,7 +608,7 @@ public class LoanAccountData {
                 maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
                 isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
                 createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
-                maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization);
+                maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled);
 
     }
 
@@ -720,6 +730,9 @@ public class LoanAccountData {
         final String closureLoanAccountNo = null;
         final BigDecimal topupAmount = null;
         final boolean isEqualAmortization = false;
+        final List<RateData> rates = null;
+        final Boolean isRatesEnabled = false;
+
         return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
                 loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
                 loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
@@ -738,7 +751,8 @@ public class LoanAccountData {
                 maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
                 isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
                 createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
-                maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization);
+                maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization,
+                rates, isRatesEnabled);
 
     }
 
@@ -768,7 +782,8 @@ public class LoanAccountData {
                 acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
                 acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
                 acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
-                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
+                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization,
+                acc.rates, acc.isRatesEnabled);
     }
 
     /**
@@ -891,6 +906,9 @@ public class LoanAccountData {
         final String closureLoanAccountNo = null;
         final BigDecimal topupAmount = null;
         final boolean isEqualAmortization = false;
+        final List<RateData> rates = null;
+        final Boolean isRatesEnabled = false;
+
         return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
                 loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
                 loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
@@ -909,7 +927,8 @@ public class LoanAccountData {
                 maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
                 isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
                 createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
-                maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization);
+                maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization,
+                rates, isRatesEnabled);
 
     }
 
@@ -939,7 +958,8 @@ public class LoanAccountData {
                 acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
                 acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
                 acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
-                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
+                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization,
+                acc.rates, acc.isRatesEnabled);
 
     }
 
@@ -1069,6 +1089,9 @@ public class LoanAccountData {
         final Long closureLoanId = null;
         final String closureLoanAccountNo = null;
         final BigDecimal topupAmount = null;
+        final List<RateData> rates = null;
+        final Boolean isRatesEnabled = false;
+
         return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
                 loanType, product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(),
                 product.getFundId(), product.getFundName(), loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -1093,7 +1116,7 @@ public class LoanAccountData {
                 originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
                 product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
                 product.getMaximumGapBetweenInstallments(), subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId,
-                closureLoanAccountNo, topupAmount, product.isEqualAmortization());
+                closureLoanAccountNo, topupAmount, product.isEqualAmortization(),rates, isRatesEnabled);
     }
 
     public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -1153,7 +1176,8 @@ public class LoanAccountData {
                 product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement,
                 paidInAdvance, acc.interestRatesPeriods, product.isVariableInstallmentsAllowed(),
                 product.getMinimumGapBetweenInstallments(), product.getMaximumGapBetweenInstallments(), acc.subStatus, acc.canUseForTopup,
-                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, product.isEqualAmortization());
+                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, product.isEqualAmortization(), acc.rates,
+                acc.isRatesEnabled);
 
     }
 
@@ -1222,6 +1246,8 @@ public class LoanAccountData {
         final PaidInAdvanceData paidInAdvance = null;
         final Collection<InterestRatePeriodData> interestRatesPeriods = null;
         final Collection<LoanAccountSummaryData> clientActiveLoanOptions = null;
+        final List<RateData> rates = null;
+        final Boolean isRatesEnabled = false;
 
         return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
                 loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
@@ -1241,7 +1267,8 @@ public class LoanAccountData {
                 outstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
                 isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
                 createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
-                maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization);
+                maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization,
+                rates, isRatesEnabled);
     }
 
     /*
@@ -1265,7 +1292,7 @@ public class LoanAccountData {
             final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
             final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges,
             final PaidInAdvanceData paidInAdvance, Collection<InterestRatePeriodData> interestRatesPeriods,
-            final Collection<LoanAccountSummaryData> clientActiveLoanOptions) {
+            final Collection<LoanAccountSummaryData> clientActiveLoanOptions,final List<RateData> rates, final Boolean isRatesEnabled) {
         LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
         if (acc.product != null) {
             loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes();
@@ -1294,12 +1321,12 @@ public class LoanAccountData {
                 acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
                 acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed,
                 acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, clientActiveLoanOptions, acc.isTopup,
-                acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
+                acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, rates, isRatesEnabled);
     }
 
     public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
             final Collection<StaffData> allowedLoanOfficers, final Collection<CalendarData> calendarOptions,
-            final Collection<PortfolioAccountData> accountLinkingOptions) {
+            final Collection<PortfolioAccountData> accountLinkingOptions, final Boolean isRatesEnabled) {
         return associationsAndTemplate(acc, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
                 acc.meeting, productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
                 acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
@@ -1307,7 +1334,7 @@ public class LoanAccountData {
                 acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
                 allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions,
                 acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance,
-                acc.interestRatesPeriods, acc.clientActiveLoanOptions);
+                acc.interestRatesPeriods, acc.clientActiveLoanOptions,acc.rates, isRatesEnabled);
     }
 
     public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
@@ -1336,7 +1363,8 @@ public class LoanAccountData {
                 acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
                 acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
                 acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
-                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
+                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates,
+                acc.isRatesEnabled);
     }
 
     public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
@@ -1401,7 +1429,8 @@ public class LoanAccountData {
                 acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
                 acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
                 acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
-                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
+                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates,
+                acc.isRatesEnabled);
 
     }
 
@@ -1435,7 +1464,8 @@ public class LoanAccountData {
                 acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule,
                 acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
                 acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
-                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
+                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates,
+                acc.isRatesEnabled);
     }
 
     public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
@@ -1463,7 +1493,8 @@ public class LoanAccountData {
                 acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
                 acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
                 acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
-                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
+                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization,
+                acc.rates, acc.isRatesEnabled);
     }
 
     public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
@@ -1492,7 +1523,8 @@ public class LoanAccountData {
                 acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule,
                 acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
                 acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
-                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
+                acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization,
+                acc.rates, acc.isRatesEnabled);
     }
 
     private LoanAccountData(final Long id, //
@@ -1542,7 +1574,8 @@ public class LoanAccountData {
             final Collection<InterestRatePeriodData> interestRatesPeriods, final Boolean isVariableInstallmentsAllowed,
             final Integer minimumGap, final Integer maximumGap, final EnumOptionData subStatus, final Boolean canUseForTopup,
             final Collection<LoanAccountSummaryData> clientActiveLoanOptions, final boolean isTopup,
-            final Long closureLoanId, final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization) {
+            final Long closureLoanId, final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization,
+            final List<RateData> rates, final Boolean isRatesEnabled) {
 
         this.id = id;
         this.accountNo = accountNo;
@@ -1622,6 +1655,7 @@ public class LoanAccountData {
         this.amortizationTypeOptions = amortizationTypeOptions;
         this.interestTypeOptions = interestTypeOptions;
         this.interestCalculationPeriodTypeOptions = interestCalculationPeriodTypeOptions;
+        this.isRatesEnabled = isRatesEnabled;
 
         if (CollectionUtils.isEmpty(transactionProcessingStrategyOptions)) {
             this.transactionProcessingStrategyOptions = null;
@@ -1725,6 +1759,7 @@ public class LoanAccountData {
         this.closureLoanAccountNo = closureLoanAccountNo;
         this.topupAmount = topupAmount;
         this.isEqualAmortization = isEqualAmortization;
+        this.rates = rates;
 
     }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java
index 9978bcd..3545b6c 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java
@@ -27,6 +27,17 @@ public class LoanChargePaidByData {
     private final Integer installmentNumber;
     private final Long chargeId;
     private final Long transactionId;
+    private final String name;
+
+    public LoanChargePaidByData(final Long id, final BigDecimal amount, final Integer installmentNumber, final Long chargeId,
+        final Long transactionId, String name) {
+        this.id = id;
+        this.amount = amount;
+        this.installmentNumber = installmentNumber;
+        this.chargeId = chargeId;
+        this.transactionId = transactionId;
+        this.name=name;
+    }
 
     public LoanChargePaidByData(final Long id, final BigDecimal amount, final Integer installmentNumber, final Long chargeId,
             final Long transactionId) {
@@ -35,6 +46,7 @@ public class LoanChargePaidByData {
         this.installmentNumber = installmentNumber;
         this.chargeId = chargeId;
         this.transactionId = transactionId;
+        this.name=null;
     }
 
     public Long getId() {
@@ -57,4 +69,7 @@ public class LoanChargePaidByData {
         return this.transactionId;
     }
 
+    public String getName() {
+        return name;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
index b986b34..fc157d1 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
@@ -59,6 +59,8 @@ public class LoanTransactionData {
     private final boolean manuallyReversed;
     private final LocalDate possibleNextRepaymentDate;
 
+    private Collection<LoanChargePaidByData> loanChargePaidByList;
+
     // templates
     final Collection<PaymentTypeData> paymentTypeOptions;
 
@@ -339,4 +341,12 @@ public class LoanTransactionData {
     public void setWriteOffReasonOptions(Collection<CodeValueData> writeOffReasonOptions){
         this.writeOffReasonOptions =writeOffReasonOptions;
     }
+
+    public Collection<LoanChargePaidByData> getLoanChargePaidByList() {
+        return loanChargePaidByList;
+    }
+
+    public void setLoanChargePaidByList(Collection<LoanChargePaidByData> loanChargePaidByList) {
+        this.loanChargePaidByList = loanChargePaidByList;
+    }
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index b44aa41..201d771 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -133,6 +133,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessin
 import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
 import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
 import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
+import org.apache.fineract.portfolio.rate.domain.Rate;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.joda.time.Days;
 import org.joda.time.LocalDate;
@@ -388,20 +389,24 @@ public class Loan extends AbstractPersistableCustom<Long> {
     @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true, fetch=FetchType.EAGER)
     private LoanTopupDetails loanTopupDetails;
 
+    @OneToMany(fetch = FetchType.LAZY)
+    @JoinTable(name = "m_loan_rate", joinColumns = @JoinColumn(name = "loan_id"), inverseJoinColumns = @JoinColumn(name = "rate_id"))
+    private List<Rate> rates;
+
     public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
             final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
             final LoanTransactionProcessingStrategy transactionProcessingStrategy,
             final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
             final Set<LoanCollateral> collateral, final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails,
             final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
-            final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) {
+            final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates) {
         final LoanStatus status = null;
         final Group group = null;
         final Boolean syncDisbursementWithMeeting = null;
         return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
                 loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
                 disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
-                interestRateDifferential);
+                interestRateDifferential, rates);
     }
 
     public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType,
@@ -411,13 +416,13 @@ public class Loan extends AbstractPersistableCustom<Long> {
             final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
             final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
             final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
-            final BigDecimal interestRateDifferential) {
+            final BigDecimal interestRateDifferential, final List<Rate> rates) {
         final LoanStatus status = null;
         final Client client = null;
         return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
                 loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
                 disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
-                interestRateDifferential);
+                interestRateDifferential, rates);
     }
 
     public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group,
@@ -427,12 +432,12 @@ public class Loan extends AbstractPersistableCustom<Long> {
             final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
             final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
             final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
-            final BigDecimal interestRateDifferential) {
+            final BigDecimal interestRateDifferential, final List<Rate> rates) {
         final LoanStatus status = null;
         return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
                 loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
                 disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
-                interestRateDifferential);
+                interestRateDifferential,rates);
     }
 
     protected Loan() {
@@ -445,7 +450,7 @@ public class Loan extends AbstractPersistableCustom<Long> {
             final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting,
             final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails,
             final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
-            final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) {
+            final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates) {
 
         this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
         this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
@@ -503,6 +508,9 @@ public class Loan extends AbstractPersistableCustom<Long> {
 
         this.proposedPrincipal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
 
+        //rates added here
+        this.rates = rates;
+
     }
 
     private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) {
@@ -1166,6 +1174,14 @@ public class Loan extends AbstractPersistableCustom<Long> {
         this.collateral.addAll(associateWithThisLoan(loanCollateral));
     }
 
+    public void updateLoanRates(final List<Rate> loanRates) {
+        if (this.rates == null) {
+            this.rates = new ArrayList<>();
+        }
+        this.rates.clear();
+        this.rates.addAll(loanRates);
+    }
+
     public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule, AppUser currentUser) {
         this.repaymentScheduleInstallments.clear();
         for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
@@ -6533,4 +6549,12 @@ public class Loan extends AbstractPersistableCustom<Long> {
     }
 
     public boolean isIndividualLoan(){return AccountType.fromInt(this.loanType).isIndividualAccount();}
+
+    public void setRates(List<Rate> rates) {
+        this.rates = rates;
+    }
+
+    public List<Rate> getRates() {
+        return rates;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
index 8c85637..3478989 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java
@@ -67,7 +67,8 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
             LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName,
             LoanApiConstants.isFloatingInterestRateParameterName, LoanApiConstants.interestRateDifferentialParameterName,
             LoanApiConstants.repaymentFrequencyNthDayTypeParameterName, LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName,
-            LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam));
+            LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam,
+            LoanProductConstants.ratesParamName));
 
     private final FromJsonHelper fromApiJsonHelper;
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
index 0d36e4b..24992bc 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
@@ -91,7 +91,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
             LoanApiConstants.linkAccountIdParameterName, LoanApiConstants.disbursementDataParameterName,
             LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName,
             LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName,
-            LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam));
+            LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.ratesParamName));
 
     private final FromJsonHelper fromApiJsonHelper;
     private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
index b068558..9995a39 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
@@ -126,6 +126,7 @@ import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataVa
 import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
 import org.apache.fineract.portfolio.note.domain.Note;
 import org.apache.fineract.portfolio.note.domain.NoteRepository;
+import org.apache.fineract.portfolio.rate.service.RateAssembler;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
 import org.apache.fineract.useradministration.domain.AppUser;
@@ -178,6 +179,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
     private final FineractEntityToEntityMappingRepository repository;
     private final FineractEntityRelationRepository fineractEntityRelationRepository;
     private final LoanProductReadPlatformService loanProductReadPlatformService;
+    private final RateAssembler rateAssembler;
 
     @Autowired
     public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -200,7 +202,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
             final LoanScheduleAssembler loanScheduleAssembler, final LoanUtilService loanUtilService,
             final CalendarReadPlatformService calendarReadPlatformService, final GlobalConfigurationRepositoryWrapper globalConfigurationRepository,
             final FineractEntityToEntityMappingRepository repository, final FineractEntityRelationRepository fineractEntityRelationRepository,
-            final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, final LoanProductReadPlatformService loanProductReadPlatformService) {
+            final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, final LoanProductReadPlatformService loanProductReadPlatformService,
+            final RateAssembler rateAssembler) {
         this.context = context;
         this.fromJsonHelper = fromJsonHelper;
         this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -236,7 +239,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
         this.repository = repository;
         this.fineractEntityRelationRepository = fineractEntityRelationRepository;
         this.loanProductReadPlatformService = loanProductReadPlatformService;
-
+        this.rateAssembler = rateAssembler;
     }
 
     private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -858,6 +861,12 @@ public void checkForProductMixRestrictions(final Loan loan) {
                 existingLoanApplication.recalculateAllCharges();
             }
 
+            //Changes to modify loan rates.
+            if (command.hasParameter(LoanProductConstants.ratesParamName)) {
+                existingLoanApplication.updateLoanRates(rateAssembler.fromParsedJson(command.parsedJson()));
+            }
+
+
             this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(existingLoanApplication.getTermFrequency(),
                     existingLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(),
                     productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue(),
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
index 1892efe..094b2e8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
@@ -25,6 +25,8 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+
+import com.google.gson.JsonArray;
 import org.apache.fineract.infrastructure.codes.domain.CodeValue;
 import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
 import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
@@ -71,6 +73,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementData
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
@@ -78,6 +81,8 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessin
 import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
 import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
 import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.apache.fineract.portfolio.rate.domain.Rate;
+import org.apache.fineract.portfolio.rate.service.RateAssembler;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.joda.time.LocalDate;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -104,6 +109,7 @@ public class LoanAssembler {
     private final ConfigurationDomainService configurationDomainService;
     private final WorkingDaysRepositoryWrapper workingDaysRepository;
     private final LoanUtilService loanUtilService;
+    private final RateAssembler rateAssembler;
 
     @Autowired
     public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanRepositoryWrapper loanRepository,
@@ -115,7 +121,7 @@ public class LoanAssembler {
             final CollateralAssembler loanCollateralAssembler, final LoanSummaryWrapper loanSummaryWrapper,
             final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
             final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
-            final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanUtilService loanUtilService) {
+            final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanUtilService loanUtilService, RateAssembler rateAssembler) {
         this.fromApiJsonHelper = fromApiJsonHelper;
         this.loanRepository = loanRepository;
         this.loanProductRepository = loanProductRepository;
@@ -134,6 +140,7 @@ public class LoanAssembler {
         this.configurationDomainService = configurationDomainService;
         this.workingDaysRepository = workingDaysRepository;
         this.loanUtilService = loanUtilService;
+        this.rateAssembler = rateAssembler;
     }
 
     public Loan assembleFrom(final Long accountId) {
@@ -222,6 +229,9 @@ public class LoanAssembler {
         Client client = null;
         Group group = null;
 
+        //Here we add Rates to LoanApplication
+        final List<Rate> rates = this.rateAssembler.fromParsedJson(element);
+
         final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element);
 
         final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element);
@@ -250,21 +260,21 @@ public class LoanAssembler {
             loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(),
                     loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges,
                     collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
-                    createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential);
+                    createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates);
 
         } else if (group != null) {
 
             loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
                     loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral,
                     syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
-                    createStandingInstructionAtDisbursement,isFloatingInterestRate, interestRateDifferential);
+                    createStandingInstructionAtDisbursement,isFloatingInterestRate, interestRateDifferential, rates);
 
         } else if (client != null) {
 
             loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund,
                     loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral,
                     fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement,
-                    isFloatingInterestRate, interestRateDifferential);
+                    isFloatingInterestRate, interestRateDifferential, rates);
 
         }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformService.java
new file mode 100644
index 0000000..1ca6dc1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformService.java
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.loanaccount.service;
+
+import java.util.List;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
+
+public interface LoanChargePaidByReadPlatformService {
+
+    List<LoanChargePaidByData> getLoanChargesPaidByTransactionId(Long id);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java
new file mode 100644
index 0000000..60cafe0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanChargePaidByReadPlatformServiceImpl implements
+    LoanChargePaidByReadPlatformService {
+
+  private final JdbcTemplate jdbcTemplate;
+  private final PlatformSecurityContext context;
+
+  @Autowired
+  public LoanChargePaidByReadPlatformServiceImpl(final PlatformSecurityContext context,
+      final RoutingDataSource dataSource) {
+    this.context = context;
+    this.jdbcTemplate = new JdbcTemplate(dataSource);
+
+  }
+
+  @Override
+  public List<LoanChargePaidByData> getLoanChargesPaidByTransactionId(Long transactionId) {
+    this.context.authenticatedUser();
+    final LoanChargePaidByMapper rm = new LoanChargePaidByMapper();
+    final String sql = "select " + rm.loanChargePaidBySchema() + " where lcpd.loan_transaction_id = ?";
+    return this.jdbcTemplate.query(sql, rm, new Object[]{transactionId});
+  }
+
+  private static final class LoanChargePaidByMapper implements RowMapper<LoanChargePaidByData> {
+
+    public String loanChargePaidBySchema() {
+      return "lcpd.id as id, lcpd.amount as amount, lcpd.installment_number as installmentNumber," +
+          " lcpd.loan_charge_id as chargeId, lcpd.loan_transaction_id as transactionId, " +
+          " c.name as chargeName"
+          + " from m_loan_charge_paid_by lcpd"
+          + " join m_loan_charge lc on lc.id=lcpd.loan_charge_Id"
+          + " join m_charge c on c.id=lc.charge_id";
+    }
+
+    @Override
+    public LoanChargePaidByData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum)
+        throws SQLException {
+      final Long id = rs.getLong("id");
+      final BigDecimal amount = rs.getBigDecimal("amount");
+      final Integer installmentNumber = rs.getInt("installmentNumber");
+      final Long chargeId = rs.getLong("chargeId");
+      final Long transactionId = rs.getLong("transactionId");
+      final String chargeName = rs.getString("chargeName");
+      return new LoanChargePaidByData(id, amount, installmentNumber, chargeId, transactionId, chargeName);
+    }
+
+  }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
index 471d4f9..068ee9e 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
@@ -134,5 +134,6 @@ public interface LoanProductConstants {
 
     public static final String isEqualAmortizationParam = "isEqualAmortization";
 
+    public static final String ratesParamName = "rates";
 
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
index 376ea36..7d79246 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
@@ -52,6 +52,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToG
 import org.apache.fineract.commands.domain.CommandWrapper;
 import org.apache.fineract.commands.service.CommandWrapperBuilder;
 import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
 import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
@@ -77,6 +78,8 @@ import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatfor
 import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
 import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService;
+import org.apache.fineract.portfolio.rate.data.RateData;
+import org.apache.fineract.portfolio.rate.service.RateReadService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
@@ -102,7 +105,8 @@ public class LoanProductsApiResource {
             "interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions",
             "accountingRuleOptions", "accountingMappingOptions", "floatingRateOptions", "isLinkedToFloatingInterestRates",
             "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate",
-            "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam));
+            "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam,
+            LoanProductConstants.ratesParamName));
 
     private final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(Arrays.asList("productId", "productName", "restrictedProducts",
             "allowedProducts", "productOptions"));
@@ -125,6 +129,9 @@ public class LoanProductsApiResource {
     private final DropdownReadPlatformService commonDropdownReadPlatformService;
     private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
     private final FloatingRatesReadPlatformService floatingRateReadPlatformService;
+    private final RateReadService rateReadService;
+    private final ConfigurationDomainService configurationDomainService;
+
 
     @Autowired
     public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService,
@@ -139,7 +146,8 @@ public class LoanProductsApiResource {
             final ProductMixReadPlatformService productMixReadPlatformService,
             final DropdownReadPlatformService commonDropdownReadPlatformService,
             PaymentTypeReadPlatformService paymentTypeReadPlatformService,
-            final FloatingRatesReadPlatformService floatingRateReadPlatformService) {
+            final FloatingRatesReadPlatformService floatingRateReadPlatformService, final RateReadService rateReadService,
+            final ConfigurationDomainService configurationDomainService) {
         this.context = context;
         this.loanProductReadPlatformService = readPlatformService;
         this.chargeReadPlatformService = chargeReadPlatformService;
@@ -156,6 +164,8 @@ public class LoanProductsApiResource {
         this.commonDropdownReadPlatformService = commonDropdownReadPlatformService;
         this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
         this.floatingRateReadPlatformService = floatingRateReadPlatformService;
+        this.rateReadService = rateReadService;
+        this.configurationDomainService = configurationDomainService;
     }
 
     @POST
@@ -288,6 +298,12 @@ public class LoanProductsApiResource {
             penaltyOptions = null;
         }
 
+        boolean isRatesEnabled = this.configurationDomainService.isSubRatesEnabled();
+        Collection<RateData> rateOptions = this.rateReadService.retrieveLoanApplicableRates();
+        if(rateOptions.isEmpty()){
+            rateOptions = null;
+        }
+
         final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
         final List<EnumOptionData> amortizationTypeOptions = this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions();
         final List<EnumOptionData> interestTypeOptions = this.dropdownReadPlatformService.retrieveLoanInterestTypeOptions();
@@ -331,11 +347,11 @@ public class LoanProductsApiResource {
 
         return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions,
                 amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions,
-                interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions,
+                interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, rateOptions, accountOptions,
                 accountingRuleTypeOptions, loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions,
                 interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions,
                 preCloseInterestCalculationStrategyOptions, floatingRateOptions, interestRecalculationNthDayTypeOptions,
-                interestRecalculationDayOfWeekTypeOptions);
+                interestRecalculationDayOfWeekTypeOptions, isRatesEnabled);
     }
 
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index 92e4b5a..dd9b83f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -47,6 +47,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
 import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
+import org.apache.fineract.portfolio.rate.data.RateData;
 import org.joda.time.LocalDate;
 import org.springframework.util.CollectionUtils;
 
@@ -133,6 +134,10 @@ public class LoanProductData implements Serializable {
     private Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
     private Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
 
+    // rates
+    private final boolean isRatesEnabled;
+    private final Collection<RateData> rates;
+
     // template related
     private final Collection<FundData> fundOptions;
     @SuppressWarnings("unused")
@@ -146,6 +151,7 @@ public class LoanProductData implements Serializable {
     private final List<EnumOptionData> interestCalculationPeriodTypeOptions;
     private final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions;
     private final Collection<ChargeData> chargeOptions;
+    private final Collection<RateData> rateOptions;
     @SuppressWarnings("unused")
     private final Collection<ChargeData> penaltyOptions;
     @SuppressWarnings("unused")
@@ -261,6 +267,9 @@ public class LoanProductData implements Serializable {
         final boolean syncExpectedWithDisbursementDate = false;
         final boolean canUseForTopup = false;
         final boolean isEqualAmortization = false;
+        final Collection<RateData> rateOptions = null;
+        final Collection<RateData> rates = null;
+        final boolean isRatesEnabled= false;
         return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
                 numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
                 minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
@@ -275,7 +284,7 @@ public class LoanProductData implements Serializable {
                 loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName,
                 interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate,
                 isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
-                syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
+                syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled);
 
     }
 
@@ -353,6 +362,9 @@ public class LoanProductData implements Serializable {
         final boolean syncExpectedWithDisbursementDate = false;
         final boolean canUseForTopup = false;
         final boolean isEqualAmortization = false;
+        final Collection<RateData> rateOptions = null;
+        final Collection<RateData> rates = null;
+        final boolean isRatesEnabled = false;
 
         return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
                 numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -368,7 +380,7 @@ public class LoanProductData implements Serializable {
                 loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName,
                 interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate,
                 isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
-                syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
+                syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled);
 
     }
 
@@ -453,6 +465,9 @@ public class LoanProductData implements Serializable {
         final boolean syncExpectedWithDisbursementDate = false;
         final boolean canUseForTopup = false;
         final boolean isEqualAmortization = false;
+        final Collection<RateData> rateOptions = null;
+        final Collection<RateData> rates = null;
+        final boolean isRatesEnabled = false;
 
         return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
                 numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -468,7 +483,7 @@ public class LoanProductData implements Serializable {
                 installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId,
                 floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
                 maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
-                syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
+                syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled);
 
     }
 
@@ -547,6 +562,9 @@ public class LoanProductData implements Serializable {
         final boolean syncExpectedWithDisbursementDate = false;
         final boolean canUseForTopup = false;
         final boolean isEqualAmortization = false;
+        final Collection<RateData> rateOptions = null;
+        final Collection<RateData> rates = null;
+        final boolean isRatesEnabled = false;
 
         return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
                 numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -562,7 +580,7 @@ public class LoanProductData implements Serializable {
                 installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId,
                 floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
                 maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
-                syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
+                syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled);
 
     }
 
@@ -601,7 +619,8 @@ public class LoanProductData implements Serializable {
                            BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, BigDecimal maxDifferentialLendingRate,
                            boolean isFloatingInterestRateCalculationAllowed, final boolean isVariableInstallmentsAllowed,
                            final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments,
-                           final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization) {
+                           final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization, Collection<RateData> rateOptions, Collection<RateData> rates,
+                           final boolean isRatesEnabled) {
         this.id = id;
         this.name = name;
         this.shortName = shortName;
@@ -653,6 +672,9 @@ public class LoanProductData implements Serializable {
         this.status = status;
         this.externalId = externalId;
         this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
+        this.rateOptions = rateOptions;
+        this.rates = rates;
+        this.isRatesEnabled = isRatesEnabled;
 
         this.chargeOptions = null;
         this.penaltyOptions = null;
@@ -715,7 +737,7 @@ public class LoanProductData implements Serializable {
                            final List<EnumOptionData> interestTypeOptions, final List<EnumOptionData> interestCalculationPeriodTypeOptions,
                            final List<EnumOptionData> repaymentFrequencyTypeOptions, final List<EnumOptionData> interestRateFrequencyTypeOptions,
                            final Collection<FundData> fundOptions, final Collection<TransactionProcessingStrategyData> transactionStrategyOptions,
-                           final Map<String, List<GLAccountData>> accountingMappingOptions, final List<EnumOptionData> accountingRuleOptions,
+                           final Collection<RateData> rateOptions, final Map<String, List<GLAccountData>> accountingMappingOptions, final List<EnumOptionData> accountingRuleOptions,
                            final List<EnumOptionData> valueConditionTypeOptions, final List<EnumOptionData> daysInMonthTypeOptions,
                            final List<EnumOptionData> daysInYearTypeOptions, final List<EnumOptionData> interestRecalculationCompoundingTypeOptions,
                            final List<EnumOptionData> rescheduleStrategyTypeOptions, final List<EnumOptionData> interestRecalculationFrequencyTypeOptions,
@@ -780,6 +802,7 @@ public class LoanProductData implements Serializable {
         this.currency = productData.currency;
         this.fundOptions = fundOptions;
         this.transactionProcessingStrategyOptions = transactionStrategyOptions;
+        this.rateOptions = rateOptions;
         this.floatingRateOptions = floatingRateOptions;
         if (this.transactionProcessingStrategyOptions != null && this.transactionProcessingStrategyOptions.size() == 1) {
             final List<TransactionProcessingStrategyData> listOfOptions = new ArrayList<>(this.transactionProcessingStrategyOptions);
@@ -843,6 +866,8 @@ public class LoanProductData implements Serializable {
         this.syncExpectedWithDisbursementDate = productData.syncExpectedWithDisbursementDate;
         this.canUseForTopup = productData.canUseForTopup;
         this.isEqualAmortization = productData.isEqualAmortization;
+        this.rates = productData.rates;
+        this.isRatesEnabled = isRatesEnabled;
     }
 
     private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -1198,9 +1223,9 @@ public class LoanProductData implements Serializable {
         return syncExpectedWithDisbursementDate;
     }
 
-    public boolean canUseForTopup() {
-        return this.canUseForTopup;
-    }
+        public boolean canUseForTopup() {
+            return this.canUseForTopup;
+        }
 
     public BigDecimal getInterestRateDifferential() {
         return this.interestRateDifferential;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
index 8aaaa2d..d64dd56 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
@@ -61,6 +61,7 @@ import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
 import org.apache.fineract.portfolio.fund.domain.Fund;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
 import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.rate.domain.Rate;
 import org.joda.time.LocalDate;
 
 /**
@@ -99,6 +100,10 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
     @JoinTable(name = "m_product_loan_charge", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "charge_id"))
     private List<Charge> charges;
 
+    @ManyToMany(fetch = FetchType.LAZY)
+    @JoinTable(name = "m_product_loan_rate", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "rate_id"))
+    private List<Rate> rates;
+
     @Embedded
     private LoanProductRelatedDetail loanProductRelatedDetail;
 
@@ -184,7 +189,8 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
     private boolean isEqualAmortization = false;
 
     public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
-            final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) {
+            final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate,
+            final List<Rate> productRates) {
 
         final String name = command.stringValueOfParameterNamed("name");
         final String shortName = command.stringValueOfParameterNamed(LoanProductConstants.shortName);
@@ -350,7 +356,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
                 installmentAmountInMultiplesOf, loanConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRate,
                 interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate,
                 isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGapBetweenInstallments,
-                maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
+                maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, productRates);
 
     }
 
@@ -580,7 +586,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
             BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate,
             Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed,
             final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments,
-            final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization) {
+            final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization, final List<Rate> rates) {
         this.fund = fund;
         this.transactionProcessingStrategy = transactionProcessingStrategy;
         this.name = name.trim();
@@ -658,6 +664,10 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
                 syncExpectedWithDisbursementDate;
         this.canUseForTopup = canUseForTopup;
         this.isEqualAmortization = isEqualAmortization;
+
+        if(rates != null){
+            this.rates = rates;
+        }
     }
 
     public MonetaryCurrency getCurrency() {
@@ -699,6 +709,25 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
         return updated;
     }
 
+    public boolean updateRates(final List<Rate> newProductRates) {
+        if (newProductRates == null) { return false; }
+
+        boolean updated = false;
+        if (this.rates != null) {
+            final Set<Rate> currentSetOfCharges = new HashSet<>(this.rates);
+            final Set<Rate> newSetOfCharges = new HashSet<>(newProductRates);
+
+            if (!currentSetOfCharges.equals(newSetOfCharges)) {
+                updated = true;
+                this.rates = newProductRates;
+            }
+        } else {
+            updated = true;
+            this.rates = newProductRates;
+        }
+        return updated;
+    }
+
     public Integer getAccountingType() {
         return this.accountingRule;
     }
@@ -1043,6 +1072,13 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
             this.canUseForTopup = newValue;
         }
 
+        if (command.hasParameter(LoanProductConstants.ratesParamName)) {
+            final JsonArray jsonArray = command.arrayOfParameterNamed(LoanProductConstants.ratesParamName);
+            if (jsonArray != null) {
+                actualChanges.put(LoanProductConstants.ratesParamName, command.jsonFragment(LoanProductConstants.ratesParamName));
+            }
+        }
+
         return actualChanges;
     }
 
@@ -1379,4 +1415,13 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
         this.isEqualAmortization = isEqualAmortization;
     }
 
+
+    public List<Rate> getRates() {
+        return rates;
+    }
+
+    public void setRates(List<Rate> rates) {
+        this.rates = rates;
+    }
+
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java
index 73a239f..1e8ece9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java
@@ -163,4 +163,12 @@ public class LoanProductMinMaxConstraints {
         this.maxNominalInterestRatePerPeriod = null;
     }
 
+
+    public void setMinNominalInterestRatePerPeriod(BigDecimal minNominalInterestRatePerPeriod) {
+        this.minNominalInterestRatePerPeriod = minNominalInterestRatePerPeriod;
+    }
+
+    public void setMaxNominalInterestRatePerPeriod(BigDecimal maxNominalInterestRatePerPeriod) {
+        this.maxNominalInterestRatePerPeriod = maxNominalInterestRatePerPeriod;
+    }
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
index cc0860a..5846f72 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
@@ -666,4 +666,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
         this.isEqualAmortization = isEqualAmortization;
     }
 
+    public void setNominalInterestRatePerPeriod(BigDecimal nominalInterestRatePerPeriod) {
+        this.nominalInterestRatePerPeriod = nominalInterestRatePerPeriod;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index 4794298..d676f9a 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -106,7 +106,8 @@ public final class LoanProductDataValidator {
             LoanProductConstants.recalculationRestFrequencyWeekdayParamName,
             LoanProductConstants.recalculationRestFrequencyNthDayParamName, LoanProductConstants.recalculationRestFrequencyOnDayParamName,
             LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName,
-            LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam));
+            LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam,
+            LoanProductConstants.ratesParamName));
 
     private static final String[] supportedloanConfigurableAttributes = {LoanProductConstants.amortizationTypeParamName,
             LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyIdParamName,
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
index cb62839..36a8c4c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
@@ -42,6 +42,8 @@ import org.apache.fineract.portfolio.loanproduct.data.LoanProductInterestRecalcu
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductParamType;
 import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
+import org.apache.fineract.portfolio.rate.data.RateData;
+import org.apache.fineract.portfolio.rate.service.RateReadService;
 import org.joda.time.LocalDate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
@@ -55,16 +57,18 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
     private final PlatformSecurityContext context;
     private final JdbcTemplate jdbcTemplate;
     private final ChargeReadPlatformService chargeReadPlatformService;
+    private final RateReadService rateReadService;
     private final FineractEntityAccessUtil fineractEntityAccessUtil;
 
     @Autowired
     public LoanProductReadPlatformServiceImpl(final PlatformSecurityContext context,
             final ChargeReadPlatformService chargeReadPlatformService, final RoutingDataSource dataSource,
-            final FineractEntityAccessUtil fineractEntityAccessUtil) {
+            final FineractEntityAccessUtil fineractEntityAccessUtil, final RateReadService rateReadService) {
         this.context = context;
         this.chargeReadPlatformService = chargeReadPlatformService;
         this.jdbcTemplate = new JdbcTemplate(dataSource);
         this.fineractEntityAccessUtil = fineractEntityAccessUtil;
+        this.rateReadService=rateReadService;
     }
 
     @Override
@@ -72,8 +76,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
 
         try {
             final Collection<ChargeData> charges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanProductId);
+            final Collection<RateData> rates = this.rateReadService.retrieveProductLoanRates(loanProductId);
             final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas = retrieveLoanProductBorrowerCycleVariations(loanProductId);
-            final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas);
+            final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas, rates);
             final String sql = "select " + rm.loanProductSchema() + " where lp.id = ?";
 
             return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanProductId });
@@ -95,7 +100,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
 
         this.context.authenticatedUser();
 
-        final LoanProductMapper rm = new LoanProductMapper(null, null);
+        final LoanProductMapper rm = new LoanProductMapper(null, null, null);
 
         String sql = "select " + rm.loanProductSchema();
 
@@ -172,10 +177,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
 
         private final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas;
 
+        private final Collection<RateData> rates;
+
         public LoanProductMapper(final Collection<ChargeData> charges,
-                final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas) {
+                final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas, final Collection<RateData> rates) {
             this.charges = charges;
             this.borrowerCycleVariationDatas = borrowerCycleVariationDatas;
+            this.rates = rates;
         }
 
         public String loanProductSchema() {
@@ -448,6 +456,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
             final boolean syncExpectedWithDisbursementDate = rs.getBoolean("syncExpectedWithDisbursementDate");
 
             final boolean canUseForTopup = rs.getBoolean("canUseForTopup");
+            final Collection<RateData> rateOptions= null;
+            final boolean isRatesEnabled = false;
 
             return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
                     numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -464,7 +474,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
                     installmentAmountInMultiplesOf, allowAttributeOverrides, isLinkedToFloatingInterestRates, floatingRateId,
                     floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
                     maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap,
-                    maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
+                    maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates, isRatesEnabled);
         }
     }
 
@@ -534,7 +544,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
     public Collection<LoanProductData> retrieveAllLoanProductsForCurrency(String currencyCode) {
         this.context.authenticatedUser();
 
-        final LoanProductMapper rm = new LoanProductMapper(null, null);
+        final LoanProductMapper rm = new LoanProductMapper(null, null, null);
 
         String sql = "select " + rm.loanProductSchema() + " where lp.currency_code= ? ";
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
index c37087b..17e00a6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
@@ -49,6 +49,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository;
 import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
 import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
@@ -57,6 +58,8 @@ import org.apache.fineract.portfolio.loanproduct.exception.LoanProductCannotBeMo
 import org.apache.fineract.portfolio.loanproduct.exception.LoanProductDateException;
 import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
 import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
+import org.apache.fineract.portfolio.rate.domain.Rate;
+import org.apache.fineract.portfolio.rate.domain.RateRepositoryWrapper;
 import org.joda.time.LocalDate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -76,6 +79,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
     private final FundRepository fundRepository;
     private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository;
     private final ChargeRepositoryWrapper chargeRepository;
+    private final RateRepositoryWrapper rateRepository;
     private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
     private final FineractEntityAccessUtil fineractEntityAccessUtil;
     private final FloatingRateRepositoryWrapper floatingRateRepository;
@@ -87,7 +91,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
             final LoanProductDataValidator fromApiJsonDeserializer, final LoanProductRepository loanProductRepository,
             final AprCalculator aprCalculator, final FundRepository fundRepository,
             final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository,
-            final ChargeRepositoryWrapper chargeRepository,
+            final ChargeRepositoryWrapper chargeRepository, final RateRepositoryWrapper rateRepository,
             final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
             final FineractEntityAccessUtil fineractEntityAccessUtil,
             final FloatingRateRepositoryWrapper floatingRateRepository,
@@ -100,6 +104,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
         this.fundRepository = fundRepository;
         this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
         this.chargeRepository = chargeRepository;
+        this.rateRepository = rateRepository;
         this.accountMappingWritePlatformService = accountMappingWritePlatformService;
         this.fineractEntityAccessUtil = fineractEntityAccessUtil;
         this.floatingRateRepository = floatingRateRepository;
@@ -125,6 +130,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
 
             final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
             final List<Charge> charges = assembleListOfProductCharges(command, currencyCode);
+            final List<Rate> rates = assembleListOfProductRates(command);
 
             FloatingRate floatingRate = null;
             if(command.parameterExists("floatingRatesId")){
@@ -132,7 +138,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
                         .findOneWithNotFoundDetection(command.longValueOfParameterNamed("floatingRatesId"));
             }
             final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command,
-                    this.aprCalculator, floatingRate);
+                    this.aprCalculator, floatingRate, rates);
             loanproduct.updateLoanProductInRelatedClasses();
 
             this.loanProductRepository.save(loanproduct);
@@ -237,6 +243,14 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
                     .updateLoanProductToGLAccountMapping(product.getId(), command, accountingTypeChanged, product.getAccountingType());
             changes.putAll(accountingMappingChanges);
 
+            if (changes.containsKey(LoanProductConstants.ratesParamName)) {
+                final List<Rate> productRates = assembleListOfProductRates(command);
+                final boolean updated = product.updateRates(productRates);
+                if (!updated) {
+                    changes.remove(LoanProductConstants.ratesParamName);
+                }
+            }
+
             if (!changes.isEmpty()) {
                 this.loanProductRepository.saveAndFlush(product);
             }
@@ -299,6 +313,28 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
         return charges;
     }
 
+    private List<Rate> assembleListOfProductRates(final JsonCommand command) {
+
+        final List<Rate> rates = new ArrayList<>();
+
+        if (command.parameterExists("rates")) {
+            final JsonArray ratesArray = command.arrayOfParameterNamed("rates");
+            if (ratesArray != null) {
+                List<Long> idList = new ArrayList<>();
+                for (int i = 0; i < ratesArray.size(); i++) {
+
+                    final JsonObject jsonObject = ratesArray.get(i).getAsJsonObject();
+                    if (jsonObject.has("id")) {
+                        idList.add(jsonObject.get("id").getAsLong());
+                    }
+                }
+                rates.addAll(this.rateRepository.findMultipleWithNotFoundDetection(idList));
+            }
+        }
+
+        return rates;
+    }
+
     /*
      * Guaranteed to throw an exception no matter what the data integrity issue
      * is.
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiConstants.java
new file mode 100644
index 0000000..20d64d6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiConstants.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.api;
+
+public class RateApiConstants {
+
+  public static final String approveUserIdParamName = "approveUserId";
+  public static final String rate = "rate";
+  public static final String rateName = "name";
+  public static final String ratePercentage = "percentage";
+  public static final String rateProductApply = "productApply";
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiResource.java
new file mode 100644
index 0000000..8015831
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiResource.java
@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.rate.data.RateData;
+import org.apache.fineract.portfolio.rate.service.RateReadService;
+import org.apache.fineract.portfolio.rate.service.RateWriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.Collection;
+
+/**
+ * Bowpi GT Created by Jose on 19/07/2017.
+ */
+
+@Path("/rates")
+@Component
+@Scope("singleton")
+public class RateApiResource {
+
+  private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
+      Arrays.asList("id", "name", "percentage", "productApply", "active"));
+  private final String resourceNameForPermissions = "RATE";
+  private final PlatformSecurityContext context;
+  private final RateReadService readPlatformService;
+  private final DefaultToApiJsonSerializer<RateData> toApiJsonSerializer;
+  private final ApiRequestParameterHelper apiRequestParameterHelper;
+  private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+  @Autowired
+  public RateApiResource(final PlatformSecurityContext context,
+      final RateReadService rateReadService,
+      final DefaultToApiJsonSerializer<RateData> toApiJsonSerializer,
+      final ApiRequestParameterHelper apiRequestParameterHelper,
+      final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+    this.context = context;
+    this.readPlatformService = rateReadService;
+    this.toApiJsonSerializer = toApiJsonSerializer;
+    this.apiRequestParameterHelper = apiRequestParameterHelper;
+    this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+  }
+
+  @GET
+  @Path("{rateId}")
+  @Consumes({MediaType.APPLICATION_JSON})
+  @Produces({MediaType.APPLICATION_JSON})
+  public String retrieveRate(@PathParam("rateId") Long rateId, @Context final UriInfo uriInfo) {
+
+    this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+    final RateData rate = this.readPlatformService.retrieveOne(rateId);
+
+    final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+        .process(uriInfo.getQueryParameters());
+
+    return this.toApiJsonSerializer.serialize(settings, rate, this.RESPONSE_DATA_PARAMETERS);
+  }
+
+  @POST
+  @Consumes({MediaType.APPLICATION_JSON})
+  @Produces({MediaType.APPLICATION_JSON})
+  public String createRate(final String apiRequestBodyAsJson) {
+    final CommandWrapper commandRequest = new CommandWrapperBuilder().createRate()
+        .withJson(apiRequestBodyAsJson).build();
+
+    final CommandProcessingResult result = this.commandsSourceWritePlatformService
+        .logCommandSource(commandRequest);
+
+    return this.toApiJsonSerializer.serialize(result);
+
+  }
+
+  @GET
+  @Consumes({MediaType.APPLICATION_JSON})
+  @Produces({MediaType.APPLICATION_JSON})
+  public String getAllRates(@Context final UriInfo uriInfo) {
+
+    this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+
+    Collection<RateData> rates = this.readPlatformService.retrieveAllRates();
+
+    final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+        .process(uriInfo.getQueryParameters());
+
+    return this.toApiJsonSerializer.serialize(settings, rates, this.RESPONSE_DATA_PARAMETERS);
+  }
+
+  @PUT
+  @Path("{rateId}")
+  @Consumes({MediaType.APPLICATION_JSON})
+  @Produces({MediaType.APPLICATION_JSON})
+  public String updateRate(@PathParam("rateId") Long rateId, final String apiRequestBodyAsJson) {
+    final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRate(rateId)
+        .withJson(apiRequestBodyAsJson).build();
+
+    final CommandProcessingResult result = this.commandsSourceWritePlatformService
+        .logCommandSource(commandRequest);
+
+    return this.toApiJsonSerializer.serialize(result);
+  }
+
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/data/RateData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/data/RateData.java
new file mode 100644
index 0000000..d60c8dd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/data/RateData.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * Bowpi GT Created by Jose on 19/07/2017.
+ */
+public class RateData implements Serializable {
+
+  private Long id;
+
+  private String name;
+
+  private BigDecimal percentage;
+
+  private String productApply;
+
+  private boolean active;
+
+  public static RateData instance(final Long id, final String name, final BigDecimal percentage,
+      final String productApply, final boolean active) {
+    return new RateData(id, name, percentage, productApply, active);
+  }
+
+  private RateData(final Long id, final String name, final BigDecimal percentage,
+      final String productApply, final boolean active) {
+    this.id = id;
+    this.name = name;
+    this.percentage = percentage;
+    this.productApply = productApply;
+    this.active = active;
+  }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/Rate.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/Rate.java
new file mode 100644
index 0000000..3d0803b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/Rate.java
@@ -0,0 +1,201 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+import javax.persistence.*;
+import java.math.BigDecimal;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+
+/**
+ * Bowpi GT Created by Jose on 19/07/2017.
+ */
+
+@Entity
+@Table(name = "m_rate", uniqueConstraints = {
+    @UniqueConstraint(columnNames = {"name"}, name = "name")})
+public class Rate extends AbstractAuditableCustom<AppUser, Long> {
+
+  @Column(name = "name", length = 250, unique = true)
+  private String name;
+
+  @Column(name = "percentage", scale = 10, precision = 2, nullable = false)
+  private BigDecimal percentage;
+
+  @Column(name = "product_apply", length = 100)
+  private String productApply;
+
+  @Column(name = "active", nullable = false)
+  private boolean active;
+
+  @ManyToOne
+  @JoinColumn(name = "approve_user", nullable = true)
+  private AppUser approveUser;
+
+
+  public Rate() {
+  }
+
+
+  public Rate(String name, BigDecimal percentage, String productApply, boolean active,
+      AppUser approveUser) {
+    this.name = name;
+    this.percentage = percentage;
+    this.productApply = productApply;
+    this.active = active;
+    this.approveUser = approveUser;
+  }
+
+  public Rate(String name, BigDecimal percentage, String productApply, boolean active) {
+    this.name = name;
+    this.percentage = percentage;
+    this.productApply = productApply;
+    this.active = active;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public BigDecimal getPercentage() {
+    return percentage;
+  }
+
+  public void setPercentage(BigDecimal percentage) {
+    this.percentage = percentage;
+  }
+
+  public boolean isActive() {
+    return active;
+  }
+
+  public void setActive(boolean active) {
+    this.active = active;
+  }
+
+  public AppUser getApproveUser() {
+    return approveUser;
+  }
+
+  public void setApproveUser(AppUser approveUser) {
+    this.approveUser = approveUser;
+  }
+
+  public String getProductApply() {
+    return productApply;
+  }
+
+  public void setProductApply(String productApply) {
+    this.productApply = productApply;
+  }
+
+  @Override
+  public String toString() {
+    return "Rate{" +
+        "name='" + name + '\'' +
+        ", percentage=" + percentage +
+        ", productApply='" + productApply + '\'' +
+        ", active=" + active +
+        ", approveUser=" + approveUser +
+        '}';
+  }
+
+  public static Rate from(String name, BigDecimal percentage, String productApply, Boolean active) {
+    return new Rate(name, percentage, productApply, active);
+  }
+
+  public static Rate fromJson(final JsonCommand command, AppUser user) {
+
+    final String name = command.stringValueOfParameterNamed("name");
+
+    final BigDecimal percentage = command.bigDecimalValueOfParameterNamed("percentage");
+
+    final String productApply = command.stringValueOfParameterNamed("productApply");
+
+    final boolean active = command.booleanPrimitiveValueOfParameterNamed("active");
+
+    return new Rate(name, percentage, productApply, active, user);
+  }
+
+  public Map<String, Object> update(final JsonCommand command) {
+
+    final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+    final String nameParamName = "name";
+    if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
+      final String newValue = command.stringValueOfParameterNamed(nameParamName);
+      actualChanges.put(nameParamName, newValue);
+      this.name = StringUtils.defaultIfEmpty(newValue, null);
+    }
+
+    final String percentageParamName = "percentage";
+    if (command.isChangeInBigDecimalParameterNamed(percentageParamName, this.percentage)) {
+      final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(percentageParamName);
+      actualChanges.put(percentageParamName, newValue);
+      this.percentage = newValue;
+    }
+
+    final String productApplyParamName = "productApply";
+    if (command.isChangeInStringParameterNamed(productApplyParamName, this.productApply)) {
+      final String newValue = command.stringValueOfParameterNamed(productApplyParamName);
+      actualChanges.put(productApplyParamName, newValue);
+      this.productApply = StringUtils.defaultIfEmpty(newValue, null);
+    }
+
+    final String activeParamName = "active";
+    if (command.isChangeInBooleanParameterNamed(activeParamName, this.active)) {
+      final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(activeParamName);
+      actualChanges.put(activeParamName, newValue);
+      this.active = newValue;
+    }
+
+    final String approveUserParamName = "approveUserId";
+    if (command.isChangeInLongParameterNamed(approveUserParamName, getApproveUserId())) {
+      final Long newValue = command.longValueOfParameterNamed(approveUserParamName);
+      actualChanges.put(approveUserParamName, newValue);
+    }
+
+    return actualChanges;
+  }
+
+  private Long getApproveUserId() {
+    Long approveUserId = null;
+    if (this.approveUser != null) {
+      approveUserId = this.approveUser.getId();
+    }
+    return approveUserId;
+  }
+
+  public void assembleFrom(String name, BigDecimal percentage, String productApply, boolean active){
+    this.name = name;
+    this.percentage = percentage;
+    this.productApply = productApply;
+    this.active = active;
+  }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepository.java
new file mode 100644
index 0000000..e593ac8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepository.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import java.util.List;
+
+/**
+ * Bowpi GT
+ * Rate repository to save on m_rate table (custom change for Credi Chapin)
+ * Created by Jose on 19/07/2017.
+ */
+public interface RateRepository extends JpaRepository<Rate, Long>, JpaSpecificationExecutor<Rate> {
+
+    Rate findByName(String name);
+    List<Rate> findAllByActiveAndProductApply(boolean active, String productApply);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepositoryWrapper.java
new file mode 100644
index 0000000..7604a11
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepositoryWrapper.java
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.apache.fineract.portfolio.rate.exception.RateNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class RateRepositoryWrapper {
+
+  private final RateRepository repository;
+
+  @Autowired
+  public RateRepositoryWrapper(final RateRepository repository) {
+    this.repository = repository;
+  }
+
+  public Rate findOneWithNotFoundDetection(final Long rateId) {
+
+    final Rate rate = this.repository.findOne(rateId);
+    if (rate == null) {
+      throw new RateNotFoundException(rateId);
+    }
+
+    return rate;
+  }
+
+  public List<Rate> findMultipleWithNotFoundDetection(final List<Long> rateIds) {
+    List<Rate> rates = new ArrayList<>();
+    if (rateIds != null && !rateIds.isEmpty()) {
+      final List<Rate> foundRates = this.repository.findAll(rateIds);
+      for (Long rateId : rateIds) {
+        Boolean found = false;
+        for (Rate foundRate : foundRates) {
+          if (Objects.equals(foundRate.getId(),
+              rateId)) {
+            found = true;
+            break;
+          }
+        }
+        if (!found) {
+          throw new RateNotFoundException(rateId);
+        }
+      }
+      rates.addAll(foundRates);
+    }
+    return rates;
+  }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateAlreadyExistException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateAlreadyExistException.java
new file mode 100644
index 0000000..f0a844e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateAlreadyExistException.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.exception;
+
+/**
+ * Bowpi GT
+ * Created by Jose on 24/07/2017.
+ */
+public class RateAlreadyExistException extends Exception{
+
+    public RateAlreadyExistException(String s) {
+        super(s);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateNotFoundException.java
new file mode 100644
index 0000000..48cf904
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateNotFoundException.java
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class RateNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+
+  public RateNotFoundException(final Long id) {
+    super("error.msg.rate.id.invalid", "Rate with identifier " + id + " does not exist", id);
+  }
+
+  public RateNotFoundException(final String name) {
+    super("error.msg.rate.id.invalid", "Rate with name " + name + " does not exist", name);
+  }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/CreateRateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/CreateRateCommandHandler.java
new file mode 100644
index 0000000..8794540
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/CreateRateCommandHandler.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.rate.service.RateWriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * Bowpi GT
+ * Created by Jose on 19/07/2017.
+ */
+@Service
+@CommandType(entity = "RATE", action = "CREATE")
+public class CreateRateCommandHandler implements NewCommandSourceHandler {
+
+  private final RateWriteService writePlatformService;
+
+  @Autowired
+  public CreateRateCommandHandler(final RateWriteService writePlatformService) {
+    this.writePlatformService = writePlatformService;
+  }
+
+  @Override
+  public CommandProcessingResult processCommand(final JsonCommand command) {
+    return this.writePlatformService.createRate(command);
+  }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/UpdateRateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/UpdateRateCommandHandler.java
new file mode 100644
index 0000000..cc22e53
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/UpdateRateCommandHandler.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.rate.service.RateWriteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Bowpi GT
+ * Created by Jose on 19/07/2017.
+ */
+@Service
+@CommandType(entity = "RATE", action = "UPDATE")
+public class UpdateRateCommandHandler implements NewCommandSourceHandler {
+
+  private final RateWriteService writePlatformService;
+
+  @Autowired
+  public UpdateRateCommandHandler(final RateWriteService writePlatformService) {
+    this.writePlatformService = writePlatformService;
+  }
+
+  @Transactional
+  @Override
+  public CommandProcessingResult processCommand(final JsonCommand command) {
+
+    return this.writePlatformService.updateRate(command.entityId(), command);
+  }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/serialization/RateDefinitionCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/serialization/RateDefinitionCommandFromApiJsonDeserializer.java
new file mode 100644
index 0000000..acfbc72
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/serialization/RateDefinitionCommandFromApiJsonDeserializer.java
@@ -0,0 +1,129 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.serialization;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.rate.api.RateApiConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class RateDefinitionCommandFromApiJsonDeserializer {
+
+  /**
+   * The parameters supported for this command.
+   */
+  private final Set<String> supportedParameters = new HashSet<>(
+      Arrays.asList("id", "name", "percentage", "productApply", "active", "approveUser", "locale"));
+
+  private final FromJsonHelper fromApiJsonHelper;
+
+  @Autowired
+  public RateDefinitionCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
+    this.fromApiJsonHelper = fromApiJsonHelper;
+  }
+
+  public void validateForCreate(final String json) {
+    if (StringUtils.isBlank(json)) {
+      throw new InvalidJsonException();
+    }
+
+    final Type typeOfMap = new TypeToken<Map<String, Object>>() {
+    }.getType();
+
+    this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
+
+    final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+
+    final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+        .resource(RateApiConstants.rateName);
+
+    final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+    final String name = this.fromApiJsonHelper.extractStringNamed(RateApiConstants.rateName, element);
+    baseDataValidator.reset().parameter(RateApiConstants.rateName).value(name).notBlank().notExceedingLengthOf(250);
+
+    final BigDecimal percentage = this.fromApiJsonHelper
+        .extractBigDecimalWithLocaleNamed(RateApiConstants.ratePercentage, element);
+    baseDataValidator.reset().parameter(RateApiConstants.ratePercentage).value(percentage).notBlank();
+
+    final String productApply = this.fromApiJsonHelper.extractStringNamed(RateApiConstants.rateProductApply, element);
+    baseDataValidator.reset().parameter(RateApiConstants.rateProductApply).value(productApply).notBlank()
+        .notExceedingLengthOf(100);
+
+    throwExceptionIfValidationWarningsExist(dataValidationErrors);
+  }
+
+  public void validateForUpdate(final String json) {
+    if (StringUtils.isBlank(json)) {
+      throw new InvalidJsonException();
+    }
+
+    final Type typeOfMap = new TypeToken<Map<String, Object>>() {
+    }.getType();
+    this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
+
+    final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+    final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
+        .resource(RateApiConstants.rate);
+
+    final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+    if (this.fromApiJsonHelper.parameterExists(RateApiConstants.rateName, element)) {
+      final String name = this.fromApiJsonHelper.extractStringNamed(RateApiConstants.rateName, element);
+      baseDataValidator.reset().parameter(RateApiConstants.rateName).value(name).notBlank().notExceedingLengthOf(250);
+    }
+
+    if (this.fromApiJsonHelper.parameterExists(RateApiConstants.ratePercentage, element)) {
+      final BigDecimal percentage = this.fromApiJsonHelper
+          .extractBigDecimalWithLocaleNamed(RateApiConstants.ratePercentage, element);
+      baseDataValidator.reset().parameter(RateApiConstants.ratePercentage).value(percentage).notBlank();
+    }
+
+    if (this.fromApiJsonHelper.parameterExists(RateApiConstants.rateProductApply, element)) {
+      final String productApply = this.fromApiJsonHelper.extractStringNamed(RateApiConstants.rateProductApply, element);
+      baseDataValidator.reset().parameter(RateApiConstants.rateProductApply).value(productApply).notBlank();
+    }
+
+    throwExceptionIfValidationWarningsExist(dataValidationErrors);
+  }
+
+  private void throwExceptionIfValidationWarningsExist(
+      final List<ApiParameterError> dataValidationErrors) {
+    if (!dataValidationErrors.isEmpty()) {
+      throw new PlatformApiDataValidationException(dataValidationErrors);
+    }
+  }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java
new file mode 100644
index 0000000..9b2d6f9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
+import org.apache.fineract.portfolio.rate.domain.Rate;
+import org.apache.fineract.portfolio.rate.domain.RateRepositoryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class RateAssembler {
+
+  private final FromJsonHelper fromApiJsonHelper;
+  private final RateRepositoryWrapper rateRepository;
+
+  @Autowired
+  public RateAssembler(final FromJsonHelper fromApiJsonHelper,
+      final RateRepositoryWrapper rateRepository) {
+    this.fromApiJsonHelper = fromApiJsonHelper;
+    this.rateRepository = rateRepository;
+  }
+
+  public List<Rate> fromParsedJson(final JsonElement element) {
+
+    final List<Rate> rateItems = new ArrayList<>();
+
+    if (element.isJsonObject()) {
+      final JsonObject topLevelJsonElement = element.getAsJsonObject();
+      final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
+
+      if (topLevelJsonElement.has(LoanProductConstants.ratesParamName) && topLevelJsonElement
+          .get(LoanProductConstants.ratesParamName)
+          .isJsonArray()) {
+        final JsonArray array = topLevelJsonElement.get(LoanProductConstants.ratesParamName)
+            .getAsJsonArray();
+        List<Long> idList = new ArrayList<>();
+
+        for (int i = 0; i < array.size(); i++) {
+
+          final JsonObject rateElement = array.get(i).getAsJsonObject();
+
+          final Long id = this.fromApiJsonHelper.extractLongNamed("id", rateElement);
+
+          if (id != null) {
+            final Long rateId = this.fromApiJsonHelper.extractLongNamed("id", rateElement);
+            idList.add(rateId);
+          }
+        }
+        rateItems.addAll(rateRepository.findMultipleWithNotFoundDetection(idList));
+      }
+    }
+
+    return rateItems;
+  }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadService.java
new file mode 100644
index 0000000..7b4b051
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadService.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.service;
+
+import org.apache.fineract.portfolio.rate.data.RateData;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Bowpi GT
+ * Created by Jose on 19/07/2017.
+ */
+public interface RateReadService {
+
+  Collection<RateData> retrieveAllRates();
+
+  Collection<RateData> retrieveLoanApplicableRates();
+
+  RateData retrieveOne(Long rateId);
+
+  RateData retrieveByName(String name);
+
+  List<RateData> retrieveProductLoanRates(Long loanId);
+
+  List<RateData> retrieveLoanRates(Long loanId);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java
new file mode 100644
index 0000000..44682de
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java
@@ -0,0 +1,150 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.service;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.rate.exception.RateNotFoundException;
+import org.apache.fineract.portfolio.rate.domain.Rate;
+import org.apache.fineract.portfolio.rate.data.RateData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Bowpi GT Created by Jose on 19/07/2017.
+ */
+@Service
+public class RateReadServiceImpl implements RateReadService {
+
+  private final JdbcTemplate jdbcTemplate;
+  private final PlatformSecurityContext context;
+
+  @Autowired
+  public RateReadServiceImpl(PlatformSecurityContext context, final RoutingDataSource dataSource) {
+    this.context = context;
+    this.jdbcTemplate = new JdbcTemplate(dataSource);
+  }
+
+  @Override
+  public Collection<RateData> retrieveAllRates() {
+    this.context.authenticatedUser();
+    final RateMapper rm = new RateMapper();
+    final String sql = "select " + rm.rateSchema();
+    return this.jdbcTemplate.query(sql, rm, new Object[]{});
+  }
+
+  @Override
+  public RateData retrieveOne(Long rateId) {
+    try {
+      this.context.authenticatedUser();
+      final RateMapper rm = new RateMapper();
+      final String sql = "select " + rm.rateSchema() + " where r.id = ?";
+      final RateData selectedRate = this.jdbcTemplate.queryForObject(sql, rm, new Object[]{rateId});
+      return selectedRate;
+
+    } catch (final EmptyResultDataAccessException e) {
+      throw new RateNotFoundException(rateId);
+    }
+  }
+
+  @Override
+  public RateData retrieveByName(String name) {
+    try {
+      this.context.authenticatedUser();
+      final RateMapper rm = new RateMapper();
+      final String sql = "select " + rm.rateSchema() + " where r.name = ?";
+      final RateData selectedRate = this.jdbcTemplate.queryForObject(sql, rm, new Object[]{name});
+      return selectedRate;
+
+    } catch (final EmptyResultDataAccessException e) {
+      throw new RateNotFoundException(name);
+    }
+  }
+
+  @Override
+  public Collection<RateData> retrieveLoanApplicableRates() {
+    this.context.authenticatedUser();
+    final RateMapper rm = new RateMapper();
+    final String sql = "select " + rm.rateSchema() + " where r.active = ? and product_apply=?";
+    return this.jdbcTemplate.query(sql, rm, new Object[]{true, "m_loan"});
+  }
+
+  @Override
+  public List<RateData> retrieveLoanRates(Long loanId) {
+    final RateMapper rm = new RateMapper();
+    final String sql = "select " + rm.loanRateSchema() + " where lr.loan_id = ?";
+    return this.jdbcTemplate.query(sql, rm, new Object[]{loanId});
+  }
+
+  @Override
+  public List<RateData> retrieveProductLoanRates(Long loanId) {
+    final RateMapper rm = new RateMapper();
+    final String sql = "select " + rm.productLoanRateSchema() + " where lr.product_loan_id = ?";
+    return this.jdbcTemplate.query(sql, rm, new Object[]{loanId});
+  }
+
+  private static final class RateMapper implements RowMapper<RateData> {
+
+
+    public String rateSchema() {
+      return " r.id as id, r.name as name, r.percentage as percentage, " +
+          "r.product_apply as productApply, r.active as active from m_rate r ";
+    }
+
+    public String loanRateSchema() {
+      return rateSchema() + " join m_loan_rate lr on lr.rate_id = r.id";
+    }
+
+    public String productLoanRateSchema() {
+      return rateSchema() + " join m_product_loan_rate lr on lr.rate_id = r.id";
+    }
+
+    public RateMapper() {
+    }
+
+    @Override
+    public RateData mapRow(ResultSet resultSet, int i) throws SQLException {
+      final Long id = resultSet.getLong("id");
+      final String name = resultSet.getString("name");
+      final BigDecimal percentage = resultSet.getBigDecimal("percentage");
+      final String productApply = resultSet.getString("productApply");
+      final boolean active = resultSet.getBoolean("active");
+      return RateData.instance(id, name, percentage, productApply, active);
+    }
+
+    public RateData mapRow(Rate rateResponse, int i) {
+      final Long id = rateResponse.getId();
+      final String name = rateResponse.getName();
+      final BigDecimal percentage = rateResponse.getPercentage();
+      final String productApply = rateResponse.getProductApply();
+      final boolean active = rateResponse.isActive();
+      return RateData.instance(id, name, percentage, productApply, active);
+    }
+  }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteService.java
new file mode 100644
index 0000000..0cd7dfe
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteService.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+/**
+ * Bowpi GT
+ * Created by Jose on 19/07/2017.
+ */
+public interface RateWriteService {
+
+    CommandProcessingResult createRate(final JsonCommand command);
+
+    CommandProcessingResult updateRate(Long rateId, JsonCommand command);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java
new file mode 100644
index 0000000..51f97b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java
@@ -0,0 +1,157 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.fineract.portfolio.rate.service;
+
+import static org.apache.fineract.portfolio.rate.api.RateApiConstants.approveUserIdParamName;
+
+import java.util.Map;
+import javax.persistence.PersistenceException;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.rate.domain.Rate;
+import org.apache.fineract.portfolio.rate.domain.RateRepository;
+import org.apache.fineract.portfolio.rate.exception.RateNotFoundException;
+import org.apache.fineract.portfolio.rate.serialization.RateDefinitionCommandFromApiJsonDeserializer;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.AppUserRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Bowpi GT Created by Jose on 19/07/2017.
+ */
+@Service
+public class RateWriteServiceImpl implements RateWriteService {
+
+  private final static Logger logger = LoggerFactory
+      .getLogger(RateWriteServiceImpl.class);
+  private final RateRepository rateRepository;
+  private final AppUserRepository appUserRepository;
+  private final PlatformSecurityContext context;
+  private final RateDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+
+  @Autowired
+  public RateWriteServiceImpl(RateRepository rateRepository, AppUserRepository appUserRepository,
+      final RateDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer,
+      PlatformSecurityContext context) {
+    this.rateRepository = rateRepository;
+    this.appUserRepository = appUserRepository;
+    this.context = context;
+    this.fromApiJsonDeserializer = fromApiJsonDeserializer;
+  }
+
+  @Override
+  public CommandProcessingResult createRate(JsonCommand command) {
+    try {
+      this.context.authenticatedUser();
+      this.fromApiJsonDeserializer.validateForCreate(command.json());
+
+      final Long approveUserId = command.longValueOfParameterNamed(approveUserIdParamName);
+      AppUser approveUser = null;
+      if (approveUserId != null) {
+        approveUser = this.appUserRepository.findOne(approveUserId);
+      }
+      final Rate rate = Rate.fromJson(command, approveUser);
+
+      this.rateRepository.save(rate);
+
+      return new CommandProcessingResultBuilder().withCommandId(command.commandId())
+          .withEntityId(rate.getId()).build();
+
+    } catch (final DataIntegrityViolationException dve) {
+      handleRateDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
+      return CommandProcessingResult.empty();
+    } catch (final PersistenceException dve) {
+      Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
+      handleRateDataIntegrityIssues(command, throwable, dve);
+      return CommandProcessingResult.empty();
+    }
+  }
+
+  @Transactional
+  @Override
+  public CommandProcessingResult updateRate(final Long rateId, final JsonCommand command) {
+    try {
+      this.context.authenticatedUser();
+
+      final Rate rateToUpdate = this.rateRepository.findOne(rateId);
+      if (rateToUpdate == null) {
+        throw new RateNotFoundException(rateId);
+      }
+
+      final Map<String, Object> changes = rateToUpdate.update(command);
+
+      this.fromApiJsonDeserializer.validateForUpdate(command.json());
+
+      if (changes.containsKey(approveUserIdParamName)) {
+        final Long newValue = (Long) changes.get(approveUserIdParamName);
+        AppUser newApproveUser = null;
+        if (newValue != null) {
+          newApproveUser = this.appUserRepository.findOne(newValue);
+        }
+        rateToUpdate.setApproveUser(newApproveUser);
+      }
+      if (!changes.isEmpty()) {
+        this.rateRepository.saveAndFlush(rateToUpdate);
+      }
+
+      return new CommandProcessingResultBuilder() //
+          .withCommandId(command.commandId()) //
+          .withEntityId(rateId) //
+          .with(changes) //
+          .build();
+
+    } catch (final DataIntegrityViolationException dve) {
+      handleRateDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
+      return new CommandProcessingResult(Long.valueOf(-1));
+    } catch (final PersistenceException dve) {
+      Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
+      handleRateDataIntegrityIssues(command, throwable, dve);
+      return CommandProcessingResult.empty();
+    }
+
+  }
+
+   /*
+   * Guaranteed to throw an exception no matter what the data integrity issue
+   * is.
+   */
+  private void handleRateDataIntegrityIssues(final JsonCommand command, final Throwable realCause,
+      final Exception dve) {
+    if (realCause.getMessage().contains("rate_name_org")) {
+      final String name = command.stringValueOfParameterNamed("name");
+      throw new PlatformDataIntegrityException("error.msg.fund.duplicate.externalId",
+          "A rate with name '" + name
+              + "' already exists", "name", name);
+    }
+
+    logger.error(dve.getMessage(), dve);
+    throw new PlatformDataIntegrityException("error.msg.fund.unknown.data.integrity.issue",
+        "Unknown data integrity issue with resource: " + realCause.getMessage());
+  }
+
+}
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V352__rates.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V352__rates.sql
new file mode 100644
index 0000000..1ca0986
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V352__rates.sql
@@ -0,0 +1,67 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you 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 IF NOT EXISTS `m_rate` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `name` varchar(250) NOT NULL,
+  `percentage` decimal(10,2) NOT NULL,
+  `active` tinyint(1) DEFAULT '0',
+  `product_apply` varchar(100) NOT NULL,
+  `created_date` datetime NULL DEFAULT NULL,
+  `createdby_id` bigint(20) NOT NULL,
+  `lastmodifiedby_id` bigint(20) NOT NULL,
+  `lastmodified_date` datetime NULL DEFAULT NULL,
+  `approve_user` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `FK_M_RATE_CREATE_USER` (`createdby_id`),
+  KEY `FK_M_RATE_APPROVE_USER` (`approve_user`),
+  CONSTRAINT `FK_M_RATE_APPROVE_USER` FOREIGN KEY (`approve_user`) REFERENCES `m_appuser` (`id`),
+  CONSTRAINT `FK_M_RATE_CREATE_USER` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`)
+);
+
+
+CREATE TABLE IF NOT EXISTS `m_loan_rate` (
+  `loan_id` bigint(20) NOT NULL,
+  `rate_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`loan_id`,`rate_id`),
+  KEY `FK_M_LOAN_RATE_RATE` (`rate_id`),
+  CONSTRAINT `FK_M_LOAN_RATE_LOAN` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
+  CONSTRAINT `FK_M_LOAN_RATE_RATE` FOREIGN KEY (`rate_id`) REFERENCES `m_rate` (`id`)
+);
+
+
+CREATE TABLE IF NOT EXISTS `m_product_loan_rate` (
+  `product_loan_id` bigint(20) NOT NULL,
+  `rate_id` bigint(20) NOT NULL,
+  PRIMARY KEY (`product_loan_id`,`rate_id`),
+  KEY `FK_M_PRODUCT_LOAN_RATE_RATE` (`rate_id`),
+  CONSTRAINT `FK_M_PRODUCT_LOAN_RATE_LOAN` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`),
+  CONSTRAINT `FK_M_PRODUCT_LOAN_RATE_RATE` FOREIGN KEY (`rate_id`) REFERENCES `m_rate` (`id`)
+);
+
+
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) VALUES
+  ('organisation', 'CREATE_RATE', 'RATE', 'CREATE', '1'),
+  ('organisation', 'UPDATE_RATE', 'RATE', 'UPDATE', '1');
+
+INSERT INTO `c_configuration`
+(`name`, `value`, `enabled`, `is_trap_door`, `description`) VALUES
+('vat-tax', 12, 0, 0, 'VAT tax'),
+('sub-rates', 12, 0, 0, 'Enable Rates Module');;


Mime
View raw message