This is an automated email from the ASF dual-hosted git repository.

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


The following commit(s) were added to refs/heads/develop by this push:
     new e1d820756f FINERACT-2180: Trigger (internal) business event when new 
datatable entry was added
e1d820756f is described below

commit e1d820756fec33ecb1174894639b20f284217c42
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Tue Feb 25 07:18:48 2025 -0500

    FINERACT-2180: Trigger (internal) business event when new datatable entry 
was added
---
 .../service/ReadWriteNonCoreDataServiceImpl.java   |  49 ++-
 .../starter/DataQueriesAutoConfiguration.java      |  10 +-
 .../datatable/DatatableEntryBusinessEvent.java     |  47 +++
 .../DatatableEntryCreatedBusinessEvent.java        |  41 ++
 .../DatatableEntryDeletedBusinessEvent.java        |  40 ++
 .../domain/datatable/DatatableEntryDetails.java    |  36 ++
 .../DatatableEntryUpdatedBusinessEvent.java        |  40 ++
 .../portfolio/client/api/ClientsApiResource.java   |   7 +-
 .../client/service/ClientReadPlatformService.java  |   2 -
 .../service/ClientReadPlatformServiceImpl.java     |  89 -----
 .../service/ClientTemplateReadPlatformService.java |  27 ++
 .../ClientTemplateReadPlatformServiceImpl.java     | 133 +++++++
 .../savings/api/SavingsAccountsApiResource.java    |   8 +-
 .../SavingsAccountReadPlatformServiceImpl.java     | 350 +----------------
 ...ingsAccountTemplateReadPlatformServiceImpl.java | 412 +++++++++++++++++++++
 .../savings/starter/SavingsConfiguration.java      |  25 +-
 .../service/DatatableBusinessEventTest.java        | 207 +++++++++++
 .../service/SavingsAccountReadPlatformService.java |   2 -
 .../SavingsAccountTemplateReadPlatformService.java |  27 ++
 19 files changed, 1081 insertions(+), 471 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
index 78796b1165..91e9a47b18 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
@@ -106,8 +106,12 @@ import 
org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
 import 
org.apache.fineract.infrastructure.dataqueries.exception.DatatableEntryRequiredException;
 import 
org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException;
 import 
org.apache.fineract.infrastructure.dataqueries.exception.DatatableSystemErrorException;
+import 
org.apache.fineract.infrastructure.event.business.domain.datatable.DatatableEntryCreatedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.datatable.DatatableEntryDeletedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.datatable.DatatableEntryDetails;
+import 
org.apache.fineract.infrastructure.event.business.domain.datatable.DatatableEntryUpdatedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
-import 
org.apache.fineract.infrastructure.security.service.SqlInjectionPreventerService;
 import org.apache.fineract.infrastructure.security.service.SqlValidator;
 import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
 import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
@@ -156,10 +160,10 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
     private final DataTableValidator dataTableValidator;
     private final ColumnValidator columnValidator;
     private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
-    private final SqlInjectionPreventerService preventSqlInjectionService;
     private final DatatableKeywordGenerator datatableKeywordGenerator;
     private final SqlValidator sqlValidator;
     private final SearchUtil searchUtil;
+    private final BusinessEventNotifierService businessEventNotifierService;
 
     @Override
     public List<DatatableData> retrieveDatatableNames(final String appTable) {
@@ -1307,8 +1311,9 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
 
         ArrayList<String> insertColumns = new ArrayList<>(
                 List.of(entityTable.getForeignKeyColumnNameOnDatatable(), 
CREATEDAT_FIELD_NAME, UPDATEDAT_FIELD_NAME));
-        LocalDateTime auditDateTime = DateUtils.getAuditLocalDateTime();
+        final LocalDateTime auditDateTime = DateUtils.getAuditLocalDateTime();
         ArrayList<Object> params = new ArrayList<>(List.of(appTableId, 
auditDateTime, auditDateTime));
+        Map<String, Object> dataObjectParams = new HashMap<String, Object>();
         for (Map.Entry<String, String> entry : dataParams.entrySet()) {
             if (isTechnicalParam(entry.getKey())) {
                 continue;
@@ -1318,8 +1323,11 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
                 continue;
             }
             insertColumns.add(columnHeader.getColumnName());
-            params.add(searchUtil.parseJdbcColumnValue(columnHeader, 
entry.getValue(), dateFormat, dateTimeFormat, locale, false,
-                    sqlGenerator));
+            Object valueParam = searchUtil.parseJdbcColumnValue(columnHeader, 
entry.getValue(), dateFormat, dateTimeFormat, locale, false,
+                    sqlGenerator);
+            params.add(valueParam);
+            dataObjectParams.put(entry.getKey(), valueParam);
+
         }
         if (addScore) {
             List<Object> scoreIds = params.stream().filter(e -> e != null && 
!String.valueOf(e).isBlank()).toList();
@@ -1353,6 +1361,11 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
             if (isMultirowDatatable(columnHeaders)) {
                 resourceId = sqlGenerator.fetchPK(keyHolder);
             }
+
+            final DatatableEntryDetails datatableEntryDetails = new 
DatatableEntryDetails(dataTableName, entityTable, resourceId,
+                    appTableId, dataObjectParams);
+            businessEventNotifierService.notifyPostBusinessEvent(new 
DatatableEntryCreatedBusinessEvent(datatableEntryDetails));
+
             return 
CommandProcessingResult.fromCommandProcessingResult(commandProcessingResult, 
resourceId);
         } catch (final DataAccessException dve) {
             handleDataIntegrityIssues(dataTableName, appTableId, 
dve.getMostSpecificCause(), dve);
@@ -1422,6 +1435,7 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
 
         final Type typeOfMap = new TypeToken<Map<String, String>>() 
{}.getType();
         final Map<String, String> dataParams = 
fromJsonHelper.extractDataMap(typeOfMap, command.json());
+        final Map<String, Object> dataObjectParams = new HashMap<String, 
Object>();
 
         final String dateFormat = dataParams.get(API_PARAM_DATE_FORMAT);
         // fall back to dateFormat to keep backward compatibility
@@ -1445,6 +1459,7 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
             Object existingValue = valuesByHeader.get(columnHeader);
             Object columnValue = searchUtil.parseColumnValue(columnHeader, 
entry.getValue(), dateFormat, dateTimeFormat, locale, false,
                     sqlGenerator);
+            dataObjectParams.put(entry.getKey(), columnValue);
             if ((columnHeader.getColumnType().isDecimalType() && 
MathUtil.isEqualTo((BigDecimal) existingValue, (BigDecimal) columnValue))
                     || (existingValue == null ? columnValue == null : 
existingValue.equals(columnValue))) {
                 log.debug("Ignore change on update {}:{}", dataTableName, 
columnName);
@@ -1457,16 +1472,23 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         Long primaryKey = datatableId == null ? appTableId : datatableId;
         if (!updateColumns.isEmpty()) {
             ResultsetColumnHeaderData pkColumn = 
searchUtil.getFiltered(columnHeaders, 
ResultsetColumnHeaderData::getIsColumnPrimaryKey);
-            params.add(primaryKey);
-            final String sql = sqlGenerator.buildUpdate(dataTableName, 
updateColumns, headersByName) + " WHERE " + pkColumn.getColumnName()
-                    + " = ?";
-            int updated = jdbcTemplate.update(sql, 
params.toArray(Object[]::new)); // NOSONAR
-            if (updated != 1) {
-                throw new 
PlatformDataIntegrityException("error.msg.invalid.update", "Expected one 
updated row.");
+            if (pkColumn != null) {
+                params.add(primaryKey);
+                final String sql = sqlGenerator.buildUpdate(dataTableName, 
updateColumns, headersByName) + " WHERE "
+                        + pkColumn.getColumnName() + " = ?";
+                int updated = jdbcTemplate.update(sql, 
params.toArray(Object[]::new)); // NOSONAR
+                if (updated != 1) {
+                    throw new 
PlatformDataIntegrityException("error.msg.invalid.update", "Expected one 
updated row.");
+                }
             }
         } else {
             log.debug("No change on update {}", dataTableName);
         }
+
+        final DatatableEntryDetails datatableEntryDetails = new 
DatatableEntryDetails(dataTableName, entityTable, datatableId, appTableId,
+                dataObjectParams);
+        businessEventNotifierService.notifyPostBusinessEvent(new 
DatatableEntryUpdatedBusinessEvent(datatableEntryDetails));
+
         return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()) //
                 .withEntityId(primaryKey) //
                 .withOfficeId(commandProcessingResult.getOfficeId()) //
@@ -1517,6 +1539,11 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
                 + whereValue;
 
         this.jdbcTemplate.update(sql); // NOSONAR
+        final Map<String, Object> dataParams = null;
+        final DatatableEntryDetails datatableEntryDetails = new 
DatatableEntryDetails(dataTableName, entityTable, datatableId, appTableId,
+                dataParams);
+        businessEventNotifierService.notifyPostBusinessEvent(new 
DatatableEntryDeletedBusinessEvent(datatableEntryDetails));
+
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
                 .withEntityId(whereValue) //
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/starter/DataQueriesAutoConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/starter/DataQueriesAutoConfiguration.java
index 8425fa997f..4de72b69ad 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/starter/DataQueriesAutoConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/starter/DataQueriesAutoConfiguration.java
@@ -29,8 +29,8 @@ import 
org.apache.fineract.infrastructure.dataqueries.service.DatatableKeywordGe
 import 
org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
 import 
org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
 import 
org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataServiceImpl;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
-import 
org.apache.fineract.infrastructure.security.service.SqlInjectionPreventerService;
 import org.apache.fineract.infrastructure.security.service.SqlValidator;
 import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
 import org.apache.fineract.portfolio.search.service.SearchUtil;
@@ -51,11 +51,11 @@ public class DataQueriesAutoConfiguration {
             final DatatableCommandFromApiJsonDeserializer 
fromApiJsonDeserializer,
             final ConfigurationDomainService configurationDomainService, final 
CodeReadPlatformService codeReadPlatformService,
             final DataTableValidator dataTableValidator, final ColumnValidator 
columnValidator,
-            final NamedParameterJdbcTemplate namedParameterJdbcTemplate, final 
SqlInjectionPreventerService preventSqlInjectionService,
-            DatatableKeywordGenerator datatableKeywordGenerator, SqlValidator 
sqlValidator, SearchUtil searchUtil) {
+            final NamedParameterJdbcTemplate namedParameterJdbcTemplate, 
DatatableKeywordGenerator datatableKeywordGenerator,
+            SqlValidator sqlValidator, SearchUtil searchUtil, final 
BusinessEventNotifierService businessEventNotifierService) {
         return new ReadWriteNonCoreDataServiceImpl(jdbcTemplate, 
databaseTypeResolver, sqlGenerator, context, fromJsonHelper,
                 genericDataService, fromApiJsonDeserializer, 
configurationDomainService, codeReadPlatformService, dataTableValidator,
-                columnValidator, namedParameterJdbcTemplate, 
preventSqlInjectionService, datatableKeywordGenerator, sqlValidator,
-                searchUtil);
+                columnValidator, namedParameterJdbcTemplate, 
datatableKeywordGenerator, sqlValidator, searchUtil,
+                businessEventNotifierService);
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryBusinessEvent.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryBusinessEvent.java
new file mode 100644
index 0000000000..3a51b060b3
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryBusinessEvent.java
@@ -0,0 +1,47 @@
+/**
+ * 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.infrastructure.event.business.domain.datatable;
+
+import lombok.Getter;
+import 
org.apache.fineract.infrastructure.event.business.domain.AbstractBusinessEvent;
+
+@Getter
+public abstract class DatatableEntryBusinessEvent extends 
AbstractBusinessEvent<DatatableEntryDetails> {
+
+    protected static final String CATEGORY = "Datatable";
+    public static final String TYPE = "DatatableEntryBusinessEvent";
+
+    protected DatatableEntryDetails datatableEntryDetails;
+
+    public DatatableEntryBusinessEvent(final DatatableEntryDetails 
datatableEntryDetails) {
+        super(datatableEntryDetails);
+        this.datatableEntryDetails = datatableEntryDetails;
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+
+    @Override
+    public String getCategory() {
+        return CATEGORY;
+    }
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryCreatedBusinessEvent.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryCreatedBusinessEvent.java
new file mode 100644
index 0000000000..a61688b42a
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryCreatedBusinessEvent.java
@@ -0,0 +1,41 @@
+/**
+ * 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.infrastructure.event.business.domain.datatable;
+
+import 
org.apache.fineract.infrastructure.event.business.domain.NoExternalEvent;
+
+public class DatatableEntryCreatedBusinessEvent extends 
DatatableEntryBusinessEvent implements NoExternalEvent {
+
+    public static final String TYPE = "DatatableEntryCreatedBusinessEvent";
+
+    public DatatableEntryCreatedBusinessEvent(final DatatableEntryDetails 
datatableEntryDetails) {
+        super(datatableEntryDetails);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+
+    @Override
+    public Long getAggregateRootId() {
+        return datatableEntryDetails.getEntityId();
+    }
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryDeletedBusinessEvent.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryDeletedBusinessEvent.java
new file mode 100644
index 0000000000..e1fa56247d
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryDeletedBusinessEvent.java
@@ -0,0 +1,40 @@
+/**
+ * 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.infrastructure.event.business.domain.datatable;
+
+import 
org.apache.fineract.infrastructure.event.business.domain.NoExternalEvent;
+
+public class DatatableEntryDeletedBusinessEvent extends 
DatatableEntryBusinessEvent implements NoExternalEvent {
+
+    public static final String TYPE = "DatatableEntryDeletedBusinessEvent";
+
+    public DatatableEntryDeletedBusinessEvent(final DatatableEntryDetails 
datatableEntryDetails) {
+        super(datatableEntryDetails);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+
+    @Override
+    public Long getAggregateRootId() {
+        return datatableEntryDetails.getEntityId();
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryDetails.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryDetails.java
new file mode 100644
index 0000000000..0aada8cc6d
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryDetails.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.infrastructure.event.business.domain.datatable;
+
+import java.util.Map;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
+
+@Data
+@RequiredArgsConstructor
+public class DatatableEntryDetails {
+
+    private final String datatableName;
+    private final EntityTables entityType;
+    private final Long entityId;
+    private final Long appTableId;
+    private final Map<String, Object> data;
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryUpdatedBusinessEvent.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryUpdatedBusinessEvent.java
new file mode 100644
index 0000000000..ed361fdd28
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/datatable/DatatableEntryUpdatedBusinessEvent.java
@@ -0,0 +1,40 @@
+/**
+ * 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.infrastructure.event.business.domain.datatable;
+
+import 
org.apache.fineract.infrastructure.event.business.domain.NoExternalEvent;
+
+public class DatatableEntryUpdatedBusinessEvent extends 
DatatableEntryBusinessEvent implements NoExternalEvent {
+
+    public static final String TYPE = "DatatableEntryUpdatedBusinessEvent";
+
+    public DatatableEntryUpdatedBusinessEvent(final DatatableEntryDetails 
datatableEntryDetails) {
+        super(datatableEntryDetails);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+
+    @Override
+    public Long getAggregateRootId() {
+        return datatableEntryDetails.getEntityId();
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java
index dab2cffbc9..7a1140235b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java
@@ -68,6 +68,7 @@ import 
org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPl
 import org.apache.fineract.portfolio.client.data.ClientData;
 import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
 import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import 
org.apache.fineract.portfolio.client.service.ClientTemplateReadPlatformService;
 import org.apache.fineract.portfolio.loanaccount.guarantor.data.ObligeeData;
 import 
org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorReadPlatformService;
 import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
@@ -85,6 +86,7 @@ public class ClientsApiResource {
 
     private final PlatformSecurityContext context;
     private final ClientReadPlatformService clientReadPlatformService;
+    private final ClientTemplateReadPlatformService 
clientTemplateReadPlatformService;
     private final ToApiJsonSerializer<ClientData> toApiJsonSerializer;
     private final ToApiJsonSerializer<AccountSummaryCollectionData> 
clientAccountSummaryToApiJsonSerializer;
     private final ApiRequestParameterHelper apiRequestParameterHelper;
@@ -122,7 +124,7 @@ public class ClientsApiResource {
         } else if (CommandParameterUtil.is(commandParam, "withdraw")) {
             clientData = 
clientReadPlatformService.retrieveAllNarrations(ClientApiConstants.CLIENT_WITHDRAW_REASON);
         } else {
-            clientData = clientReadPlatformService.retrieveTemplate(officeId, 
staffInSelectedOfficeOnly);
+            clientData = 
clientTemplateReadPlatformService.retrieveTemplate(officeId, 
staffInSelectedOfficeOnly);
         }
 
         final ApiRequestJsonSerializationSettings settings = 
apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -458,7 +460,8 @@ public class ClientsApiResource {
     private ClientData retrieveClientData(final Long clientId, final boolean 
staffInSelectedOfficeOnly, final boolean isTemplate) {
         ClientData clientData = 
clientReadPlatformService.retrieveOne(clientId);
         if (isTemplate) {
-            final ClientData templateData = 
clientReadPlatformService.retrieveTemplate(clientData.getOfficeId(), 
staffInSelectedOfficeOnly);
+            final ClientData templateData = 
clientTemplateReadPlatformService.retrieveTemplate(clientData.getOfficeId(),
+                    staffInSelectedOfficeOnly);
             clientData = ClientData.templateOnTop(clientData, templateData);
             Collection<SavingsAccountData> savingAccountOptions = 
savingsAccountReadPlatformService.retrieveForLookup(clientId, null);
             if (savingAccountOptions != null && savingAccountOptions.size() > 
0) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformService.java
index 60514e729e..5fa017e72f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformService.java
@@ -27,8 +27,6 @@ import org.apache.fineract.portfolio.client.data.ClientData;
 
 public interface ClientReadPlatformService {
 
-    ClientData retrieveTemplate(Long officeId, boolean 
staffInSelectedOfficeOnly);
-
     Page<ClientData> retrieveAll(SearchParameters searchParameters);
 
     ClientData retrieveOne(Long clientId);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
index 7f747b78e7..74d45fcb43 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
@@ -32,54 +32,36 @@ import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
 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.data.ApiParameterError;
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
 import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
 import org.apache.fineract.infrastructure.core.service.Page;
 import org.apache.fineract.infrastructure.core.service.PaginationHelper;
 import org.apache.fineract.infrastructure.core.service.SearchParameters;
 import 
org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
-import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
-import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
-import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
-import 
org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
-import org.apache.fineract.organisation.office.data.OfficeData;
-import 
org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
-import org.apache.fineract.organisation.staff.data.StaffData;
-import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
-import org.apache.fineract.portfolio.address.data.AddressData;
-import 
org.apache.fineract.portfolio.address.service.AddressReadPlatformService;
-import org.apache.fineract.portfolio.client.api.ClientApiConstants;
 import 
org.apache.fineract.portfolio.client.data.ClientCollateralManagementData;
 import org.apache.fineract.portfolio.client.data.ClientData;
-import org.apache.fineract.portfolio.client.data.ClientFamilyMembersData;
 import org.apache.fineract.portfolio.client.data.ClientNonPersonData;
 import org.apache.fineract.portfolio.client.data.ClientTimelineData;
 import org.apache.fineract.portfolio.client.domain.Client;
 import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
 import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
 import org.apache.fineract.portfolio.client.domain.ClientStatus;
-import org.apache.fineract.portfolio.client.domain.LegalForm;
 import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
 import org.apache.fineract.portfolio.client.mapper.ClientMapper;
 import 
org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
 import 
org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagementRepositoryWrapper;
 import org.apache.fineract.portfolio.group.data.GroupGeneralData;
-import org.apache.fineract.portfolio.savings.data.SavingsProductData;
-import 
org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.springframework.dao.EmptyResultDataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.stereotype.Service;
-import org.springframework.util.CollectionUtils;
 
 @Service
 @RequiredArgsConstructor
@@ -87,10 +69,7 @@ public class ClientReadPlatformServiceImpl implements 
ClientReadPlatformService
 
     private final JdbcTemplate jdbcTemplate;
     private final PlatformSecurityContext context;
-    private final OfficeReadPlatformService officeReadPlatformService;
-    private final StaffReadPlatformService staffReadPlatformService;
     private final CodeValueReadPlatformService codeValueReadPlatformService;
-    private final SavingsProductReadPlatformService 
savingsProductReadPlatformService;
     // data mappers
     private final PaginationHelper paginationHelper;
     private final DatabaseSpecificSQLGenerator sqlGenerator;
@@ -99,71 +78,11 @@ public class ClientReadPlatformServiceImpl implements 
ClientReadPlatformService
     private final ClientMembersOfGroupMapper membersOfGroupMapper = new 
ClientMembersOfGroupMapper();
     private final ParentGroupsMapper clientGroupsMapper = new 
ParentGroupsMapper();
 
-    private final AddressReadPlatformService addressReadPlatformService;
-    private final ClientFamilyMembersReadPlatformService 
clientFamilyMembersReadPlatformService;
-    private final EntityDatatableChecksReadService 
entityDatatableChecksReadService;
     private final ColumnValidator columnValidator;
     private final ClientCollateralManagementRepositoryWrapper 
clientCollateralManagementRepositoryWrapper;
-    private final ConfigurationDomainService configurationDomainService;
     private final ClientRepositoryWrapper clientRepositoryWrapper;
     private final ClientMapper clientMapper;
 
-    @Override
-    public ClientData retrieveTemplate(final Long officeId, final boolean 
staffInSelectedOfficeOnly) {
-        this.context.authenticatedUser();
-
-        final Long defaultOfficeId = defaultToUsersOfficeIfNull(officeId);
-        AddressData address = null;
-
-        final Collection<OfficeData> offices = 
this.officeReadPlatformService.retrieveAllOfficesForDropdown();
-
-        final Collection<SavingsProductData> savingsProductDatas = 
this.savingsProductReadPlatformService.retrieveAllForLookupByType(null);
-
-        final Boolean isAddressEnabled = 
configurationDomainService.isAddressEnabled();
-        if (isAddressEnabled) {
-            address = this.addressReadPlatformService.retrieveTemplate();
-        }
-
-        final ClientFamilyMembersData familyMemberOptions = 
this.clientFamilyMembersReadPlatformService.retrieveTemplate();
-
-        Collection<StaffData> staffOptions = null;
-
-        final boolean loanOfficersOnly = false;
-        if (staffInSelectedOfficeOnly) {
-            staffOptions = 
this.staffReadPlatformService.retrieveAllStaffForDropdown(defaultOfficeId);
-        } else {
-            staffOptions = 
this.staffReadPlatformService.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(defaultOfficeId,
-                    loanOfficersOnly);
-        }
-        if (CollectionUtils.isEmpty(staffOptions)) {
-            staffOptions = null;
-        }
-        final List<CodeValueData> genderOptions = new ArrayList<>(
-                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.GENDER));
-
-        final List<CodeValueData> clientTypeOptions = new ArrayList<>(
-                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_TYPE));
-
-        final List<CodeValueData> clientClassificationOptions = new 
ArrayList<>(
-                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_CLASSIFICATION));
-
-        final List<CodeValueData> clientNonPersonConstitutionOptions = new 
ArrayList<>(
-                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_NON_PERSON_CONSTITUTION));
-
-        final List<CodeValueData> clientNonPersonMainBusinessLineOptions = new 
ArrayList<>(
-                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_NON_PERSON_MAIN_BUSINESS_LINE));
-
-        final List<EnumOptionData> clientLegalFormOptions = 
ClientEnumerations.legalForm(LegalForm.values());
-
-        final List<DatatableData> datatableTemplates = 
this.entityDatatableChecksReadService.retrieveTemplates(StatusEnum.CREATE.getValue(),
-                EntityTables.CLIENT.getName(), null);
-
-        return ClientData.template(defaultOfficeId, 
LocalDate.now(DateUtils.getDateTimeZoneOfTenant()), offices, staffOptions, null,
-                genderOptions, savingsProductDatas, clientTypeOptions, 
clientClassificationOptions, clientNonPersonConstitutionOptions,
-                clientNonPersonMainBusinessLineOptions, 
clientLegalFormOptions, familyMemberOptions,
-                new ArrayList<AddressData>(Arrays.asList(address)), 
isAddressEnabled, datatableTemplates);
-    }
-
     @Override
     public Page<ClientData> retrieveAll(final SearchParameters 
searchParameters) {
 
@@ -643,14 +562,6 @@ public class ClientReadPlatformServiceImpl implements 
ClientReadPlatformService
         }
     }
 
-    private Long defaultToUsersOfficeIfNull(final Long officeId) {
-        Long defaultOfficeId = officeId;
-        if (defaultOfficeId == null) {
-            defaultOfficeId = 
this.context.authenticatedUser().getOffice().getId();
-        }
-        return defaultOfficeId;
-    }
-
     @Override
     public ClientData retrieveAllNarrations(final String clientNarrations) {
         final List<CodeValueData> narrations = new ArrayList<>(
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTemplateReadPlatformService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTemplateReadPlatformService.java
new file mode 100644
index 0000000000..b4ac660da7
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTemplateReadPlatformService.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.client.service;
+
+import org.apache.fineract.portfolio.client.data.ClientData;
+
+public interface ClientTemplateReadPlatformService {
+
+    ClientData retrieveTemplate(Long officeId, boolean 
staffInSelectedOfficeOnly);
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTemplateReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTemplateReadPlatformServiceImpl.java
new file mode 100644
index 0000000000..7bb563cee8
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientTemplateReadPlatformServiceImpl.java
@@ -0,0 +1,133 @@
+/**
+ * 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.client.service;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+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.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
+import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
+import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
+import 
org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService;
+import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.organisation.office.data.OfficeData;
+import 
org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.address.data.AddressData;
+import 
org.apache.fineract.portfolio.address.service.AddressReadPlatformService;
+import org.apache.fineract.portfolio.client.api.ClientApiConstants;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.data.ClientFamilyMembersData;
+import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
+import org.apache.fineract.portfolio.client.domain.LegalForm;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import 
org.apache.fineract.portfolio.savings.service.SavingsProductReadPlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+@Service
+@RequiredArgsConstructor
+public class ClientTemplateReadPlatformServiceImpl implements 
ClientTemplateReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final OfficeReadPlatformService officeReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final CodeValueReadPlatformService codeValueReadPlatformService;
+    private final SavingsProductReadPlatformService 
savingsProductReadPlatformService;
+    // data mappers
+    private final EntityDatatableChecksReadService 
entityDatatableChecksReadService;
+
+    private final AddressReadPlatformService addressReadPlatformService;
+    private final ClientFamilyMembersReadPlatformService 
clientFamilyMembersReadPlatformService;
+    private final ConfigurationDomainService configurationDomainService;
+
+    @Override
+    public ClientData retrieveTemplate(final Long officeId, final boolean 
staffInSelectedOfficeOnly) {
+        this.context.authenticatedUser();
+
+        final Long defaultOfficeId = defaultToUsersOfficeIfNull(officeId);
+        AddressData address = null;
+
+        final Collection<OfficeData> offices = 
this.officeReadPlatformService.retrieveAllOfficesForDropdown();
+
+        final Collection<SavingsProductData> savingsProductDatas = 
this.savingsProductReadPlatformService.retrieveAllForLookupByType(null);
+
+        final Boolean isAddressEnabled = 
configurationDomainService.isAddressEnabled();
+        if (isAddressEnabled) {
+            address = this.addressReadPlatformService.retrieveTemplate();
+        }
+
+        final ClientFamilyMembersData familyMemberOptions = 
this.clientFamilyMembersReadPlatformService.retrieveTemplate();
+
+        Collection<StaffData> staffOptions = null;
+
+        final boolean loanOfficersOnly = false;
+        if (staffInSelectedOfficeOnly) {
+            staffOptions = 
this.staffReadPlatformService.retrieveAllStaffForDropdown(defaultOfficeId);
+        } else {
+            staffOptions = 
this.staffReadPlatformService.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(defaultOfficeId,
+                    loanOfficersOnly);
+        }
+        if (CollectionUtils.isEmpty(staffOptions)) {
+            staffOptions = null;
+        }
+        final List<CodeValueData> genderOptions = new ArrayList<>(
+                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.GENDER));
+
+        final List<CodeValueData> clientTypeOptions = new ArrayList<>(
+                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_TYPE));
+
+        final List<CodeValueData> clientClassificationOptions = new 
ArrayList<>(
+                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_CLASSIFICATION));
+
+        final List<CodeValueData> clientNonPersonConstitutionOptions = new 
ArrayList<>(
+                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_NON_PERSON_CONSTITUTION));
+
+        final List<CodeValueData> clientNonPersonMainBusinessLineOptions = new 
ArrayList<>(
+                
this.codeValueReadPlatformService.retrieveCodeValuesByCode(ClientApiConstants.CLIENT_NON_PERSON_MAIN_BUSINESS_LINE));
+
+        final List<EnumOptionData> clientLegalFormOptions = 
ClientEnumerations.legalForm(LegalForm.values());
+
+        final List<DatatableData> datatableTemplates = 
this.entityDatatableChecksReadService.retrieveTemplates(StatusEnum.CREATE.getValue(),
+                EntityTables.CLIENT.getName(), null);
+
+        return ClientData.template(defaultOfficeId, 
LocalDate.now(DateUtils.getDateTimeZoneOfTenant()), offices, staffOptions, null,
+                genderOptions, savingsProductDatas, clientTypeOptions, 
clientClassificationOptions, clientNonPersonConstitutionOptions,
+                clientNonPersonMainBusinessLineOptions, 
clientLegalFormOptions, familyMemberOptions,
+                new ArrayList<AddressData>(Arrays.asList(address)), 
isAddressEnabled, datatableTemplates);
+    }
+
+    private Long defaultToUsersOfficeIfNull(final Long officeId) {
+        Long defaultOfficeId = officeId;
+        if (defaultOfficeId == null) {
+            defaultOfficeId = 
this.context.authenticatedUser().getOffice().getId();
+        }
+        return defaultOfficeId;
+    }
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java
index 32d6a50ae5..1aa63d0fab 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java
@@ -74,6 +74,7 @@ import 
org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
 import 
org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
+import 
org.apache.fineract.portfolio.savings.service.SavingsAccountTemplateReadPlatformService;
 import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
 import org.glassfish.jersey.media.multipart.FormDataParam;
 import org.springframework.stereotype.Component;
@@ -86,6 +87,7 @@ import org.springframework.util.CollectionUtils;
 public class SavingsAccountsApiResource {
 
     private final SavingsAccountReadPlatformService 
savingsAccountReadPlatformService;
+    private final SavingsAccountTemplateReadPlatformService 
savingsAccountTemplateReadPlatformService;
     private final PlatformSecurityContext context;
     private final DefaultToApiJsonSerializer<SavingsAccountData> 
toApiJsonSerializer;
     private final PortfolioCommandSourceWritePlatformService 
commandsSourceWritePlatformService;
@@ -112,7 +114,7 @@ public class SavingsAccountsApiResource {
 
         
context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME);
 
-        final SavingsAccountData savingsAccount = 
savingsAccountReadPlatformService.retrieveTemplate(clientId, groupId, productId,
+        final SavingsAccountData savingsAccount = 
savingsAccountTemplateReadPlatformService.retrieveTemplate(clientId, groupId, 
productId,
                 staffInSelectedOfficeOnly);
 
         final ApiRequestJsonSerializationSettings settings = 
apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -662,8 +664,8 @@ public class SavingsAccountsApiResource {
         SavingsAccountData templateData = null;
         final ApiRequestJsonSerializationSettings settings = 
apiRequestParameterHelper.process(uriInfo.getQueryParameters());
         if (settings.isTemplate()) {
-            templateData = 
savingsAccountReadPlatformService.retrieveTemplate(savingsAccount.getClientId(),
 savingsAccount.getGroupId(),
-                    savingsAccount.getSavingsProductId(), 
staffInSelectedOfficeOnly);
+            templateData = 
savingsAccountTemplateReadPlatformService.retrieveTemplate(savingsAccount.getClientId(),
+                    savingsAccount.getGroupId(), 
savingsAccount.getSavingsProductId(), staffInSelectedOfficeOnly);
         }
 
         return SavingsAccountData.withTemplateOptions(savingsAccount, 
templateData, transactions, charges);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
index 46579b8a76..22a9298d19 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -42,23 +42,12 @@ import org.apache.fineract.infrastructure.core.service.Page;
 import org.apache.fineract.infrastructure.core.service.PaginationHelper;
 import org.apache.fineract.infrastructure.core.service.SearchParameters;
 import 
org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
-import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
-import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
-import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
-import 
org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
 import org.apache.fineract.organisation.monetary.data.CurrencyData;
-import org.apache.fineract.organisation.staff.data.StaffData;
-import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
 import org.apache.fineract.portfolio.account.data.AccountTransferData;
-import org.apache.fineract.portfolio.charge.data.ChargeData;
-import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
-import 
org.apache.fineract.portfolio.charge.util.ConvertChargeDataToSpecificChargeData;
 import org.apache.fineract.portfolio.client.data.ClientData;
-import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
 import org.apache.fineract.portfolio.group.data.GroupGeneralData;
-import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
 import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
 import org.apache.fineract.portfolio.savings.DepositAccountType;
@@ -91,19 +80,12 @@ import 
org.springframework.dao.EmptyResultDataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.ResultSetExtractor;
 import org.springframework.jdbc.core.RowMapper;
-import org.springframework.util.CollectionUtils;
 
 public class SavingsAccountReadPlatformServiceImpl implements 
SavingsAccountReadPlatformService {
 
     private final PlatformSecurityContext context;
     private final JdbcTemplate jdbcTemplate;
-    private final ClientReadPlatformService clientReadPlatformService;
-    private final GroupReadPlatformService groupReadPlatformService;
-    private final SavingsProductReadPlatformService 
savingsProductReadPlatformService;
-    private final StaffReadPlatformService staffReadPlatformService;
-    private final SavingsDropdownReadPlatformService 
dropdownReadPlatformService;
     private final DatabaseSpecificSQLGenerator sqlGenerator;
-    private final ChargeReadPlatformService chargeReadPlatformService;
 
     // mappers
     private final SavingsAccountTransactionTemplateMapper 
transactionTemplateMapper;
@@ -116,35 +98,22 @@ public class SavingsAccountReadPlatformServiceImpl 
implements SavingsAccountRead
     // pagination
     private final PaginationHelper paginationHelper;
 
-    private final EntityDatatableChecksReadService 
entityDatatableChecksReadService;
     private final ColumnValidator columnValidator;
     private final SavingsAccountAssembler savingAccountAssembler;
 
     private final SavingsAccountRepositoryWrapper 
savingsAccountRepositoryWrapper;
 
     public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext 
context, final JdbcTemplate jdbcTemplate,
-            final ClientReadPlatformService clientReadPlatformService, final 
GroupReadPlatformService groupReadPlatformService,
-            final SavingsProductReadPlatformService 
savingProductReadPlatformService,
-            final StaffReadPlatformService staffReadPlatformService, final 
SavingsDropdownReadPlatformService dropdownReadPlatformService,
-            final ChargeReadPlatformService chargeReadPlatformService,
-            final EntityDatatableChecksReadService 
entityDatatableChecksReadService, final ColumnValidator columnValidator,
-            final SavingsAccountAssembler savingAccountAssembler, 
PaginationHelper paginationHelper,
+            final SavingsAccountAssembler savingAccountAssembler, 
PaginationHelper paginationHelper, ColumnValidator columnValidator,
             DatabaseSpecificSQLGenerator sqlGenerator, 
SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper) {
         this.context = context;
         this.jdbcTemplate = jdbcTemplate;
-        this.clientReadPlatformService = clientReadPlatformService;
-        this.groupReadPlatformService = groupReadPlatformService;
-        this.savingsProductReadPlatformService = 
savingProductReadPlatformService;
-        this.staffReadPlatformService = staffReadPlatformService;
-        this.dropdownReadPlatformService = dropdownReadPlatformService;
         this.sqlGenerator = sqlGenerator;
         this.savingsAccountRepositoryWrapper = savingsAccountRepositoryWrapper;
         this.transactionTemplateMapper = new 
SavingsAccountTransactionTemplateMapper();
         this.transactionsMapper = new SavingsAccountTransactionsMapper();
         this.savingsAccountTransactionsForBatchMapper = new 
SavingsAccountTransactionsForBatchMapper();
         this.savingAccountMapper = new SavingAccountMapper();
-        this.chargeReadPlatformService = chargeReadPlatformService;
-        this.entityDatatableChecksReadService = 
entityDatatableChecksReadService;
         this.columnValidator = columnValidator;
         this.paginationHelper = paginationHelper;
         this.savingAccountMapperForInterestPosting = new 
SavingAccountMapperForInterestPosting();
@@ -1033,141 +1002,6 @@ public class SavingsAccountReadPlatformServiceImpl 
implements SavingsAccountRead
         }
     }
 
-    @Override
-    public SavingsAccountData retrieveTemplate(final Long clientId, final Long 
groupId, final Long productId,
-            final boolean staffInSelectedOfficeOnly) {
-
-        final AppUser loggedInUser = this.context.authenticatedUser();
-        Long officeId = loggedInUser.getOffice().getId();
-
-        ClientData client = null;
-        if (clientId != null) {
-            client = this.clientReadPlatformService.retrieveOne(clientId);
-            officeId = client.getOfficeId();
-        }
-
-        GroupGeneralData group = null;
-        if (groupId != null) {
-            group = this.groupReadPlatformService.retrieveOne(groupId);
-            officeId = group.getOfficeId();
-        }
-
-        final Collection<SavingsProductData> productOptions = 
this.savingsProductReadPlatformService.retrieveAllForLookup();
-        SavingsAccountData template = null;
-        if (productId != null) {
-
-            final SavingAccountTemplateMapper mapper = new 
SavingAccountTemplateMapper(client, group);
-
-            final String sql = "select " + mapper.schema() + " where sp.id = 
?";
-            template = this.jdbcTemplate.queryForObject(sql, mapper, new 
Object[] { productId }); // NOSONAR
-
-            final Collection<EnumOptionData> 
interestCompoundingPeriodTypeOptions = this.dropdownReadPlatformService
-                    .retrieveCompoundingInterestPeriodTypeOptions();
-
-            final Collection<EnumOptionData> interestPostingPeriodTypeOptions 
= this.dropdownReadPlatformService
-                    .retrieveInterestPostingPeriodTypeOptions();
-
-            final Collection<EnumOptionData> interestCalculationTypeOptions = 
this.dropdownReadPlatformService
-                    .retrieveInterestCalculationTypeOptions();
-
-            final Collection<EnumOptionData> 
interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService
-                    .retrieveInterestCalculationDaysInYearTypeOptions();
-
-            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions 
= this.dropdownReadPlatformService
-                    .retrieveLockinPeriodFrequencyTypeOptions();
-
-            final Collection<EnumOptionData> withdrawalFeeTypeOptions = 
this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions();
-
-            final Collection<SavingsAccountTransactionData> transactions = 
null;
-            final Collection<ChargeData> productCharges = 
this.chargeReadPlatformService.retrieveSavingsProductCharges(productId);
-            // update charges from Product charges
-            final Collection<SavingsAccountChargeData> charges = 
fromChargesToSavingsCharges(productCharges);
-
-            final boolean feeChargesOnly = false;
-            final Collection<ChargeData> chargeOptions = 
this.chargeReadPlatformService
-                    .retrieveSavingsProductApplicableCharges(feeChargesOnly);
-
-            Collection<StaffData> fieldOfficerOptions = null;
-
-            if (officeId != null) {
-
-                if (staffInSelectedOfficeOnly) {
-                    // only bring back loan officers in selected branch/office
-                    final Collection<StaffData> fieldOfficersInBranch = 
this.staffReadPlatformService
-                            .retrieveAllLoanOfficersInOfficeById(officeId);
-
-                    if (!CollectionUtils.isEmpty(fieldOfficersInBranch)) {
-                        fieldOfficerOptions = new 
ArrayList<>(fieldOfficersInBranch);
-                    }
-                } else {
-                    // by default bring back all officers in selected
-                    // branch/office as well as officers in office above
-                    // this office
-                    final boolean restrictToLoanOfficersOnly = true;
-                    final Collection<StaffData> loanOfficersInHierarchy = 
this.staffReadPlatformService
-                            
.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeId, 
restrictToLoanOfficersOnly);
-
-                    if (!CollectionUtils.isEmpty(loanOfficersInHierarchy)) {
-                        fieldOfficerOptions = new 
ArrayList<>(loanOfficersInHierarchy);
-                    }
-                }
-            }
-
-            template = SavingsAccountData.withTemplateOptions(template, 
productOptions, fieldOfficerOptions,
-                    interestCompoundingPeriodTypeOptions, 
interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
-                    interestCalculationDaysInYearTypeOptions, 
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions,
-                    charges, chargeOptions);
-        } else {
-
-            String clientName = null;
-            if (client != null) {
-                clientName = client.getDisplayName();
-            }
-
-            String groupName = null;
-            if (group != null) {
-                groupName = group.getName();
-            }
-
-            template = SavingsAccountData.withClientTemplate(clientId, 
clientName, groupId, groupName);
-
-            final Collection<StaffData> fieldOfficerOptions = null;
-            final Collection<EnumOptionData> 
interestCompoundingPeriodTypeOptions = null;
-            final Collection<EnumOptionData> interestPostingPeriodTypeOptions 
= null;
-            final Collection<EnumOptionData> interestCalculationTypeOptions = 
null;
-            final Collection<EnumOptionData> 
interestCalculationDaysInYearTypeOptions = null;
-            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions 
= null;
-            final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
-
-            final Collection<SavingsAccountTransactionData> transactions = 
null;
-            final Collection<SavingsAccountChargeData> charges = null;
-
-            final boolean feeChargesOnly = false;
-            final Collection<ChargeData> chargeOptions = 
this.chargeReadPlatformService
-                    .retrieveSavingsProductApplicableCharges(feeChargesOnly);
-
-            template = SavingsAccountData.withTemplateOptions(template, 
productOptions, fieldOfficerOptions,
-                    interestCompoundingPeriodTypeOptions, 
interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
-                    interestCalculationDaysInYearTypeOptions, 
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions,
-                    charges, chargeOptions);
-        }
-
-        final List<DatatableData> datatableTemplates = 
this.entityDatatableChecksReadService.retrieveTemplates(StatusEnum.CREATE.getValue(),
-                EntityTables.SAVINGS.getName(), productId);
-        template.setDatatables(datatableTemplates);
-
-        return template;
-    }
-
-    private Collection<SavingsAccountChargeData> 
fromChargesToSavingsCharges(final Collection<ChargeData> productCharges) {
-        final Collection<SavingsAccountChargeData> savingsCharges = new 
ArrayList<>();
-        for (final ChargeData chargeData : productCharges) {
-            final SavingsAccountChargeData savingsCharge = 
ConvertChargeDataToSpecificChargeData.toSavingsAccountChargeData(chargeData);
-            savingsCharges.add(savingsCharge);
-        }
-        return savingsCharges;
-    }
-
     @Override
     public SavingsAccountTransactionData 
retrieveDepositTransactionTemplate(final Long savingsId,
             final DepositAccountType depositAccountType) {
@@ -1402,188 +1236,6 @@ public class SavingsAccountReadPlatformServiceImpl 
implements SavingsAccountRead
         }
     }
 
-    private static final class SavingAccountTemplateMapper implements 
RowMapper<SavingsAccountData> {
-
-        private final ClientData client;
-        private final GroupGeneralData group;
-
-        private final String schemaSql;
-
-        SavingAccountTemplateMapper(final ClientData client, final 
GroupGeneralData group) {
-            this.client = client;
-            this.group = group;
-
-            final StringBuilder sqlBuilder = new StringBuilder(400);
-            sqlBuilder.append("sp.id as productId, sp.name as productName, ");
-            sqlBuilder.append(
-                    "sp.currency_code as currencyCode, sp.currency_digits as 
currencyDigits, sp.currency_multiplesof as inMultiplesOf, ");
-            sqlBuilder.append("curr.name as currencyName, 
curr.internationalized_name_code as currencyNameCode, ");
-            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, 
");
-            sqlBuilder.append("sp.nominal_annual_interest_rate as 
nominalAnnualIterestRate, ");
-            sqlBuilder.append("sp.interest_compounding_period_enum as 
interestCompoundingPeriodType, ");
-            sqlBuilder.append("sp.interest_posting_period_enum as 
interestPostingPeriodType, ");
-            sqlBuilder.append("sp.interest_calculation_type_enum as 
interestCalculationType, ");
-            sqlBuilder.append("sp.interest_calculation_days_in_year_type_enum 
as interestCalculationDaysInYearType, ");
-            sqlBuilder.append("sp.min_required_opening_balance as 
minRequiredOpeningBalance, ");
-            sqlBuilder.append("sp.lockin_period_frequency as 
lockinPeriodFrequency,");
-            sqlBuilder.append("sp.lockin_period_frequency_enum as 
lockinPeriodFrequencyType, ");
-            // sqlBuilder.append("sp.withdrawal_fee_amount as
-            // withdrawalFeeAmount,");
-            // sqlBuilder.append("sp.withdrawal_fee_type_enum as
-            // withdrawalFeeTypeEnum, ");
-            sqlBuilder.append("sp.withdrawal_fee_for_transfer as 
withdrawalFeeForTransfers, ");
-            sqlBuilder.append("sp.min_balance_for_interest_calculation as 
minBalanceForInterestCalculation, ");
-            sqlBuilder.append("sp.allow_overdraft as allowOverdraft, ");
-            sqlBuilder.append("sp.overdraft_limit as overdraftLimit, ");
-            sqlBuilder.append("sp.nominal_annual_interest_rate_overdraft as 
nominalAnnualInterestRateOverdraft, ");
-            sqlBuilder.append("sp.min_overdraft_for_interest_calculation as 
minOverdraftForInterestCalculation, ");
-            sqlBuilder.append("sp.withhold_tax as withHoldTax,");
-            sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, 
");
-
-            // sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,");
-            // sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth,
-            // ");
-            // sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay ");
-            sqlBuilder.append("sp.min_required_balance as minRequiredBalance, 
");
-            sqlBuilder.append("sp.enforce_min_required_balance as 
enforceMinRequiredBalance, ");
-            sqlBuilder.append("sp.max_allowed_lien_limit as 
maxAllowedLienLimit, ");
-            sqlBuilder.append("sp.is_lien_allowed as lienAllowed ");
-            sqlBuilder.append("from m_savings_product sp ");
-            sqlBuilder.append("join m_currency curr on curr.code = 
sp.currency_code ");
-            sqlBuilder.append("left join m_tax_group tg on tg.id = 
sp.tax_group_id  ");
-
-            this.schemaSql = sqlBuilder.toString();
-        }
-
-        public String schema() {
-            return this.schemaSql;
-        }
-
-        @Override
-        public SavingsAccountData mapRow(final ResultSet rs, 
@SuppressWarnings("unused") final int rowNum) throws SQLException {
-
-            final Long productId = rs.getLong("productId");
-            final String productName = rs.getString("productName");
-
-            final String currencyCode = rs.getString("currencyCode");
-            final String currencyName = rs.getString("currencyName");
-            final String currencyNameCode = rs.getString("currencyNameCode");
-            final String currencyDisplaySymbol = 
rs.getString("currencyDisplaySymbol");
-            final Integer currencyDigits = JdbcSupport.getInteger(rs, 
"currencyDigits");
-            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, 
"inMultiplesOf");
-            final CurrencyData currency = new CurrencyData(currencyCode, 
currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol,
-                    currencyNameCode);
-
-            final BigDecimal nominalAnnualIterestRate = 
rs.getBigDecimal("nominalAnnualIterestRate");
-
-            final EnumOptionData interestCompoundingPeriodType = 
SavingsEnumerations.compoundingInterestPeriodType(
-                    
SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, 
"interestCompoundingPeriodType")));
-
-            final EnumOptionData interestPostingPeriodType = 
SavingsEnumerations.interestPostingPeriodType(
-                    
SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, 
"interestPostingPeriodType")));
-
-            final EnumOptionData interestCalculationType = SavingsEnumerations
-                    
.interestCalculationType(SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs,
 "interestCalculationType")));
-
-            final EnumOptionData interestCalculationDaysInYearType = 
SavingsEnumerations.interestCalculationDaysInYearType(
-                    
SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, 
"interestCalculationDaysInYearType")));
-
-            final BigDecimal minRequiredOpeningBalance = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance");
-
-            final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, 
"lockinPeriodFrequency");
-            EnumOptionData lockinPeriodFrequencyType = null;
-            final Integer lockinPeriodFrequencyTypeValue = 
JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
-            if (lockinPeriodFrequencyTypeValue != null) {
-                final SavingsPeriodFrequencyType lockinPeriodType = 
SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
-                lockinPeriodFrequencyType = 
SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
-            }
-
-            // final BigDecimal withdrawalFeeAmount =
-            // rs.getBigDecimal("withdrawalFeeAmount");
-
-            /*
-             * EnumOptionData withdrawalFeeType = null; final Integer 
withdrawalFeeTypeValue =
-             * JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum"); if 
(withdrawalFeeTypeValue != null) {
-             * withdrawalFeeType = 
SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); }
-             */
-            final boolean withdrawalFeeForTransfers = 
rs.getBoolean("withdrawalFeeForTransfers");
-
-            final boolean allowOverdraft = rs.getBoolean("allowOverdraft");
-            final BigDecimal overdraftLimit = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit");
-            final BigDecimal nominalAnnualInterestRateOverdraft = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
-                    "nominalAnnualInterestRateOverdraft");
-            final BigDecimal minOverdraftForInterestCalculation = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
-                    "minOverdraftForInterestCalculation");
-
-            final BigDecimal minRequiredBalance = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance");
-            final boolean enforceMinRequiredBalance = 
rs.getBoolean("enforceMinRequiredBalance");
-            final BigDecimal maxAllowedLienLimit = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit");
-            final boolean lienAllowed = rs.getBoolean("lienAllowed");
-            final BigDecimal minBalanceForInterestCalculation = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
-                    "minBalanceForInterestCalculation");
-
-            // final BigDecimal annualFeeAmount =
-            // JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
-            // "annualFeeAmount");
-
-            /*
-             * MonthDay annualFeeOnMonthDay = null; final Integer 
annualFeeOnMonth = JdbcSupport.getInteger(rs,
-             * "annualFeeOnMonth"); final Integer annualFeeOnDay = 
JdbcSupport.getInteger(rs, "annualFeeOnDay"); if
-             * (annualFeeAmount != null && annualFeeOnDay != null) { 
annualFeeOnMonthDay = new
-             * MonthDay(annualFeeOnMonth, annualFeeOnDay); }
-             */
-
-            final boolean withHoldTax = rs.getBoolean("withHoldTax");
-            final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId");
-            final String taxGroupName = rs.getString("taxGroupName");
-            TaxGroupData taxGroupData = null;
-            if (taxGroupId != null) {
-                taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName);
-            }
-
-            Long clientId = null;
-            String clientName = null;
-            if (this.client != null) {
-                clientId = this.client.getId();
-                clientName = this.client.getDisplayName();
-            }
-
-            Long groupId = null;
-            String groupName = null;
-            if (this.group != null) {
-                groupId = this.group.getId();
-                groupName = this.group.getName();
-            }
-
-            final Long fieldOfficerId = null;
-            final String fieldOfficerName = null;
-            final SavingsAccountStatusEnumData status = null;
-            // final LocalDate annualFeeNextDueDate = null;
-            final SavingsAccountSummaryData summary = null;
-            final BigDecimal onHoldFunds = null;
-            final BigDecimal savingsAmountOnHold = null;
-
-            final SavingsAccountSubStatusEnumData subStatus = null;
-            final String reasonForBlock = null;
-            final LocalDate lastActiveTransactionDate = null;
-            final boolean isDormancyTrackingActive = false;
-            final Integer daysToInactive = null;
-            final Integer daysToDormancy = null;
-            final Integer daysToEscheat = null;
-
-            final SavingsAccountApplicationTimelineData timeline = 
SavingsAccountApplicationTimelineData.templateDefault();
-            final EnumOptionData depositType = null;
-            return SavingsAccountData.instance(null, null, depositType, null, 
groupId, groupName, clientId, clientName, productId,
-                    productName, fieldOfficerId, fieldOfficerName, status, 
subStatus, reasonForBlock, timeline, currency,
-                    nominalAnnualIterestRate, interestCompoundingPeriodType, 
interestPostingPeriodType, interestCalculationType,
-                    interestCalculationDaysInYearType, 
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
-                    withdrawalFeeForTransfers, summary, allowOverdraft, 
overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
-                    maxAllowedLienLimit, lienAllowed, 
minBalanceForInterestCalculation, onHoldFunds, 
nominalAnnualInterestRateOverdraft,
-                    minOverdraftForInterestCalculation, withHoldTax, 
taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive,
-                    daysToInactive, daysToDormancy, daysToEscheat, 
savingsAmountOnHold);
-        }
-    }
-
     @Override
     public Collection<SavingsAccountData> retrieveForLookup(Long clientId, 
Boolean overdraft) {
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountTemplateReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountTemplateReadPlatformServiceImpl.java
new file mode 100644
index 0000000000..3817eb0638
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountTemplateReadPlatformServiceImpl.java
@@ -0,0 +1,412 @@
+/**
+ * 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.savings.service;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
+import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
+import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
+import 
org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksReadService;
+import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.organisation.staff.data.StaffData;
+import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
+import org.apache.fineract.portfolio.charge.data.ChargeData;
+import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
+import 
org.apache.fineract.portfolio.charge.util.ConvertChargeDataToSpecificChargeData;
+import org.apache.fineract.portfolio.client.data.ClientData;
+import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
+import org.apache.fineract.portfolio.group.data.GroupGeneralData;
+import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
+import 
org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import 
org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import 
org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
+import 
org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
+import 
org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.apache.fineract.portfolio.tax.data.TaxGroupData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.util.CollectionUtils;
+
+public class SavingsAccountTemplateReadPlatformServiceImpl implements 
SavingsAccountTemplateReadPlatformService {
+
+    private final PlatformSecurityContext context;
+    private final JdbcTemplate jdbcTemplate;
+    private final ClientReadPlatformService clientReadPlatformService;
+    private final GroupReadPlatformService groupReadPlatformService;
+    private final SavingsProductReadPlatformService 
savingsProductReadPlatformService;
+    private final StaffReadPlatformService staffReadPlatformService;
+    private final SavingsDropdownReadPlatformService 
dropdownReadPlatformService;
+    private final ChargeReadPlatformService chargeReadPlatformService;
+
+    private final EntityDatatableChecksReadService 
entityDatatableChecksReadService;
+
+    public SavingsAccountTemplateReadPlatformServiceImpl(final 
PlatformSecurityContext context, final JdbcTemplate jdbcTemplate,
+            final ClientReadPlatformService clientReadPlatformService, final 
GroupReadPlatformService groupReadPlatformService,
+            final SavingsProductReadPlatformService 
savingProductReadPlatformService,
+            final StaffReadPlatformService staffReadPlatformService, final 
SavingsDropdownReadPlatformService dropdownReadPlatformService,
+            final ChargeReadPlatformService chargeReadPlatformService,
+            final EntityDatatableChecksReadService 
entityDatatableChecksReadService, final ColumnValidator columnValidator) {
+        this.context = context;
+        this.jdbcTemplate = jdbcTemplate;
+        this.clientReadPlatformService = clientReadPlatformService;
+        this.groupReadPlatformService = groupReadPlatformService;
+        this.savingsProductReadPlatformService = 
savingProductReadPlatformService;
+        this.staffReadPlatformService = staffReadPlatformService;
+        this.dropdownReadPlatformService = dropdownReadPlatformService;
+        this.chargeReadPlatformService = chargeReadPlatformService;
+        this.entityDatatableChecksReadService = 
entityDatatableChecksReadService;
+    }
+
+    @Override
+    public SavingsAccountData retrieveTemplate(final Long clientId, final Long 
groupId, final Long productId,
+            final boolean staffInSelectedOfficeOnly) {
+
+        final AppUser loggedInUser = this.context.authenticatedUser();
+        Long officeId = loggedInUser.getOffice().getId();
+
+        ClientData client = null;
+        if (clientId != null) {
+            client = this.clientReadPlatformService.retrieveOne(clientId);
+            officeId = client.getOfficeId();
+        }
+
+        GroupGeneralData group = null;
+        if (groupId != null) {
+            group = this.groupReadPlatformService.retrieveOne(groupId);
+            officeId = group.getOfficeId();
+        }
+
+        final Collection<SavingsProductData> productOptions = 
this.savingsProductReadPlatformService.retrieveAllForLookup();
+        SavingsAccountData template = null;
+        if (productId != null) {
+
+            final SavingAccountTemplateMapper mapper = new 
SavingAccountTemplateMapper(client, group);
+
+            final String sql = "select " + mapper.schema() + " where sp.id = 
?";
+            template = this.jdbcTemplate.queryForObject(sql, mapper, new 
Object[] { productId }); // NOSONAR
+
+            final Collection<EnumOptionData> 
interestCompoundingPeriodTypeOptions = this.dropdownReadPlatformService
+                    .retrieveCompoundingInterestPeriodTypeOptions();
+
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions 
= this.dropdownReadPlatformService
+                    .retrieveInterestPostingPeriodTypeOptions();
+
+            final Collection<EnumOptionData> interestCalculationTypeOptions = 
this.dropdownReadPlatformService
+                    .retrieveInterestCalculationTypeOptions();
+
+            final Collection<EnumOptionData> 
interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService
+                    .retrieveInterestCalculationDaysInYearTypeOptions();
+
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions 
= this.dropdownReadPlatformService
+                    .retrieveLockinPeriodFrequencyTypeOptions();
+
+            final Collection<EnumOptionData> withdrawalFeeTypeOptions = 
this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions();
+
+            final Collection<SavingsAccountTransactionData> transactions = 
null;
+            final Collection<ChargeData> productCharges = 
this.chargeReadPlatformService.retrieveSavingsProductCharges(productId);
+            // update charges from Product charges
+            final Collection<SavingsAccountChargeData> charges = 
fromChargesToSavingsCharges(productCharges);
+
+            final boolean feeChargesOnly = false;
+            final Collection<ChargeData> chargeOptions = 
this.chargeReadPlatformService
+                    .retrieveSavingsProductApplicableCharges(feeChargesOnly);
+
+            Collection<StaffData> fieldOfficerOptions = null;
+
+            if (officeId != null) {
+
+                if (staffInSelectedOfficeOnly) {
+                    // only bring back loan officers in selected branch/office
+                    final Collection<StaffData> fieldOfficersInBranch = 
this.staffReadPlatformService
+                            .retrieveAllLoanOfficersInOfficeById(officeId);
+
+                    if (!CollectionUtils.isEmpty(fieldOfficersInBranch)) {
+                        fieldOfficerOptions = new 
ArrayList<>(fieldOfficersInBranch);
+                    }
+                } else {
+                    // by default bring back all officers in selected
+                    // branch/office as well as officers in office above
+                    // this office
+                    final boolean restrictToLoanOfficersOnly = true;
+                    final Collection<StaffData> loanOfficersInHierarchy = 
this.staffReadPlatformService
+                            
.retrieveAllStaffInOfficeAndItsParentOfficeHierarchy(officeId, 
restrictToLoanOfficersOnly);
+
+                    if (!CollectionUtils.isEmpty(loanOfficersInHierarchy)) {
+                        fieldOfficerOptions = new 
ArrayList<>(loanOfficersInHierarchy);
+                    }
+                }
+            }
+
+            template = SavingsAccountData.withTemplateOptions(template, 
productOptions, fieldOfficerOptions,
+                    interestCompoundingPeriodTypeOptions, 
interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                    interestCalculationDaysInYearTypeOptions, 
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions,
+                    charges, chargeOptions);
+        } else {
+
+            String clientName = null;
+            if (client != null) {
+                clientName = client.getDisplayName();
+            }
+
+            String groupName = null;
+            if (group != null) {
+                groupName = group.getName();
+            }
+
+            template = SavingsAccountData.withClientTemplate(clientId, 
clientName, groupId, groupName);
+
+            final Collection<StaffData> fieldOfficerOptions = null;
+            final Collection<EnumOptionData> 
interestCompoundingPeriodTypeOptions = null;
+            final Collection<EnumOptionData> interestPostingPeriodTypeOptions 
= null;
+            final Collection<EnumOptionData> interestCalculationTypeOptions = 
null;
+            final Collection<EnumOptionData> 
interestCalculationDaysInYearTypeOptions = null;
+            final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions 
= null;
+            final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
+
+            final Collection<SavingsAccountTransactionData> transactions = 
null;
+            final Collection<SavingsAccountChargeData> charges = null;
+
+            final boolean feeChargesOnly = false;
+            final Collection<ChargeData> chargeOptions = 
this.chargeReadPlatformService
+                    .retrieveSavingsProductApplicableCharges(feeChargesOnly);
+
+            template = SavingsAccountData.withTemplateOptions(template, 
productOptions, fieldOfficerOptions,
+                    interestCompoundingPeriodTypeOptions, 
interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
+                    interestCalculationDaysInYearTypeOptions, 
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions,
+                    charges, chargeOptions);
+        }
+
+        final List<DatatableData> datatableTemplates = 
this.entityDatatableChecksReadService.retrieveTemplates(StatusEnum.CREATE.getValue(),
+                EntityTables.SAVINGS.getName(), productId);
+        template.setDatatables(datatableTemplates);
+
+        return template;
+    }
+
+    private static final class SavingAccountTemplateMapper implements 
RowMapper<SavingsAccountData> {
+
+        private final ClientData client;
+        private final GroupGeneralData group;
+
+        private final String schemaSql;
+
+        SavingAccountTemplateMapper(final ClientData client, final 
GroupGeneralData group) {
+            this.client = client;
+            this.group = group;
+
+            final StringBuilder sqlBuilder = new StringBuilder(400);
+            sqlBuilder.append("sp.id as productId, sp.name as productName, ");
+            sqlBuilder.append(
+                    "sp.currency_code as currencyCode, sp.currency_digits as 
currencyDigits, sp.currency_multiplesof as inMultiplesOf, ");
+            sqlBuilder.append("curr.name as currencyName, 
curr.internationalized_name_code as currencyNameCode, ");
+            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, 
");
+            sqlBuilder.append("sp.nominal_annual_interest_rate as 
nominalAnnualIterestRate, ");
+            sqlBuilder.append("sp.interest_compounding_period_enum as 
interestCompoundingPeriodType, ");
+            sqlBuilder.append("sp.interest_posting_period_enum as 
interestPostingPeriodType, ");
+            sqlBuilder.append("sp.interest_calculation_type_enum as 
interestCalculationType, ");
+            sqlBuilder.append("sp.interest_calculation_days_in_year_type_enum 
as interestCalculationDaysInYearType, ");
+            sqlBuilder.append("sp.min_required_opening_balance as 
minRequiredOpeningBalance, ");
+            sqlBuilder.append("sp.lockin_period_frequency as 
lockinPeriodFrequency,");
+            sqlBuilder.append("sp.lockin_period_frequency_enum as 
lockinPeriodFrequencyType, ");
+            // sqlBuilder.append("sp.withdrawal_fee_amount as
+            // withdrawalFeeAmount,");
+            // sqlBuilder.append("sp.withdrawal_fee_type_enum as
+            // withdrawalFeeTypeEnum, ");
+            sqlBuilder.append("sp.withdrawal_fee_for_transfer as 
withdrawalFeeForTransfers, ");
+            sqlBuilder.append("sp.min_balance_for_interest_calculation as 
minBalanceForInterestCalculation, ");
+            sqlBuilder.append("sp.allow_overdraft as allowOverdraft, ");
+            sqlBuilder.append("sp.overdraft_limit as overdraftLimit, ");
+            sqlBuilder.append("sp.nominal_annual_interest_rate_overdraft as 
nominalAnnualInterestRateOverdraft, ");
+            sqlBuilder.append("sp.min_overdraft_for_interest_calculation as 
minOverdraftForInterestCalculation, ");
+            sqlBuilder.append("sp.withhold_tax as withHoldTax,");
+            sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, 
");
+
+            // sqlBuilder.append("sp.annual_fee_amount as annualFeeAmount,");
+            // sqlBuilder.append("sp.annual_fee_on_month as annualFeeOnMonth,
+            // ");
+            // sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay ");
+            sqlBuilder.append("sp.min_required_balance as minRequiredBalance, 
");
+            sqlBuilder.append("sp.enforce_min_required_balance as 
enforceMinRequiredBalance, ");
+            sqlBuilder.append("sp.max_allowed_lien_limit as 
maxAllowedLienLimit, ");
+            sqlBuilder.append("sp.is_lien_allowed as lienAllowed ");
+            sqlBuilder.append("from m_savings_product sp ");
+            sqlBuilder.append("join m_currency curr on curr.code = 
sp.currency_code ");
+            sqlBuilder.append("left join m_tax_group tg on tg.id = 
sp.tax_group_id  ");
+
+            this.schemaSql = sqlBuilder.toString();
+        }
+
+        public String schema() {
+            return this.schemaSql;
+        }
+
+        @Override
+        public SavingsAccountData mapRow(final ResultSet rs, 
@SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long productId = rs.getLong("productId");
+            final String productName = rs.getString("productName");
+
+            final String currencyCode = rs.getString("currencyCode");
+            final String currencyName = rs.getString("currencyName");
+            final String currencyNameCode = rs.getString("currencyNameCode");
+            final String currencyDisplaySymbol = 
rs.getString("currencyDisplaySymbol");
+            final Integer currencyDigits = JdbcSupport.getInteger(rs, 
"currencyDigits");
+            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, 
"inMultiplesOf");
+            final CurrencyData currency = new CurrencyData(currencyCode, 
currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol,
+                    currencyNameCode);
+
+            final BigDecimal nominalAnnualIterestRate = 
rs.getBigDecimal("nominalAnnualIterestRate");
+
+            final EnumOptionData interestCompoundingPeriodType = 
SavingsEnumerations.compoundingInterestPeriodType(
+                    
SavingsCompoundingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, 
"interestCompoundingPeriodType")));
+
+            final EnumOptionData interestPostingPeriodType = 
SavingsEnumerations.interestPostingPeriodType(
+                    
SavingsPostingInterestPeriodType.fromInt(JdbcSupport.getInteger(rs, 
"interestPostingPeriodType")));
+
+            final EnumOptionData interestCalculationType = SavingsEnumerations
+                    
.interestCalculationType(SavingsInterestCalculationType.fromInt(JdbcSupport.getInteger(rs,
 "interestCalculationType")));
+
+            final EnumOptionData interestCalculationDaysInYearType = 
SavingsEnumerations.interestCalculationDaysInYearType(
+                    
SavingsInterestCalculationDaysInYearType.fromInt(JdbcSupport.getInteger(rs, 
"interestCalculationDaysInYearType")));
+
+            final BigDecimal minRequiredOpeningBalance = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredOpeningBalance");
+
+            final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, 
"lockinPeriodFrequency");
+            EnumOptionData lockinPeriodFrequencyType = null;
+            final Integer lockinPeriodFrequencyTypeValue = 
JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
+            if (lockinPeriodFrequencyTypeValue != null) {
+                final SavingsPeriodFrequencyType lockinPeriodType = 
SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
+                lockinPeriodFrequencyType = 
SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodType);
+            }
+
+            // final BigDecimal withdrawalFeeAmount =
+            // rs.getBigDecimal("withdrawalFeeAmount");
+
+            /*
+             * EnumOptionData withdrawalFeeType = null; final Integer 
withdrawalFeeTypeValue =
+             * JdbcSupport.getInteger(rs, "withdrawalFeeTypeEnum"); if 
(withdrawalFeeTypeValue != null) {
+             * withdrawalFeeType = 
SavingsEnumerations.withdrawalFeeType(withdrawalFeeTypeValue); }
+             */
+            final boolean withdrawalFeeForTransfers = 
rs.getBoolean("withdrawalFeeForTransfers");
+
+            final boolean allowOverdraft = rs.getBoolean("allowOverdraft");
+            final BigDecimal overdraftLimit = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "overdraftLimit");
+            final BigDecimal nominalAnnualInterestRateOverdraft = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+                    "nominalAnnualInterestRateOverdraft");
+            final BigDecimal minOverdraftForInterestCalculation = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+                    "minOverdraftForInterestCalculation");
+
+            final BigDecimal minRequiredBalance = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance");
+            final boolean enforceMinRequiredBalance = 
rs.getBoolean("enforceMinRequiredBalance");
+            final BigDecimal maxAllowedLienLimit = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit");
+            final boolean lienAllowed = rs.getBoolean("lienAllowed");
+            final BigDecimal minBalanceForInterestCalculation = 
JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+                    "minBalanceForInterestCalculation");
+
+            // final BigDecimal annualFeeAmount =
+            // JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
+            // "annualFeeAmount");
+
+            /*
+             * MonthDay annualFeeOnMonthDay = null; final Integer 
annualFeeOnMonth = JdbcSupport.getInteger(rs,
+             * "annualFeeOnMonth"); final Integer annualFeeOnDay = 
JdbcSupport.getInteger(rs, "annualFeeOnDay"); if
+             * (annualFeeAmount != null && annualFeeOnDay != null) { 
annualFeeOnMonthDay = new
+             * MonthDay(annualFeeOnMonth, annualFeeOnDay); }
+             */
+
+            final boolean withHoldTax = rs.getBoolean("withHoldTax");
+            final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId");
+            final String taxGroupName = rs.getString("taxGroupName");
+            TaxGroupData taxGroupData = null;
+            if (taxGroupId != null) {
+                taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName);
+            }
+
+            Long clientId = null;
+            String clientName = null;
+            if (this.client != null) {
+                clientId = this.client.getId();
+                clientName = this.client.getDisplayName();
+            }
+
+            Long groupId = null;
+            String groupName = null;
+            if (this.group != null) {
+                groupId = this.group.getId();
+                groupName = this.group.getName();
+            }
+
+            final Long fieldOfficerId = null;
+            final String fieldOfficerName = null;
+            final SavingsAccountStatusEnumData status = null;
+            // final LocalDate annualFeeNextDueDate = null;
+            final SavingsAccountSummaryData summary = null;
+            final BigDecimal onHoldFunds = null;
+            final BigDecimal savingsAmountOnHold = null;
+
+            final SavingsAccountSubStatusEnumData subStatus = null;
+            final String reasonForBlock = null;
+            final LocalDate lastActiveTransactionDate = null;
+            final boolean isDormancyTrackingActive = false;
+            final Integer daysToInactive = null;
+            final Integer daysToDormancy = null;
+            final Integer daysToEscheat = null;
+
+            final SavingsAccountApplicationTimelineData timeline = 
SavingsAccountApplicationTimelineData.templateDefault();
+            final EnumOptionData depositType = null;
+            return SavingsAccountData.instance(null, null, depositType, null, 
groupId, groupName, clientId, clientName, productId,
+                    productName, fieldOfficerId, fieldOfficerName, status, 
subStatus, reasonForBlock, timeline, currency,
+                    nominalAnnualIterestRate, interestCompoundingPeriodType, 
interestPostingPeriodType, interestCalculationType,
+                    interestCalculationDaysInYearType, 
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
+                    withdrawalFeeForTransfers, summary, allowOverdraft, 
overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
+                    maxAllowedLienLimit, lienAllowed, 
minBalanceForInterestCalculation, onHoldFunds, 
nominalAnnualInterestRateOverdraft,
+                    minOverdraftForInterestCalculation, withHoldTax, 
taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive,
+                    daysToInactive, daysToDormancy, daysToEscheat, 
savingsAmountOnHold);
+        }
+    }
+
+    private Collection<SavingsAccountChargeData> 
fromChargesToSavingsCharges(final Collection<ChargeData> productCharges) {
+        final Collection<SavingsAccountChargeData> savingsCharges = new 
ArrayList<>();
+        for (final ChargeData chargeData : productCharges) {
+            final SavingsAccountChargeData savingsCharge = 
ConvertChargeDataToSpecificChargeData.toSavingsAccountChargeData(chargeData);
+            savingsCharges.add(savingsCharge);
+        }
+        return savingsCharges;
+    }
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java
index c81ab4eefe..19578d472c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java
@@ -124,6 +124,8 @@ import 
org.apache.fineract.portfolio.savings.service.SavingsAccountInterestPosti
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountInterestPostingServiceImpl;
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformServiceImpl;
+import 
org.apache.fineract.portfolio.savings.service.SavingsAccountTemplateReadPlatformService;
+import 
org.apache.fineract.portfolio.savings.service.SavingsAccountTemplateReadPlatformServiceImpl;
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformServiceJpaRepositoryImpl;
 import 
org.apache.fineract.portfolio.savings.service.SavingsApplicationProcessWritePlatformService;
@@ -335,16 +337,23 @@ public class SavingsConfiguration {
     @Bean
     @ConditionalOnMissingBean(SavingsAccountReadPlatformService.class)
     public SavingsAccountReadPlatformService 
savingsAccountReadPlatformService(PlatformSecurityContext context, JdbcTemplate 
jdbcTemplate,
-            ClientReadPlatformService clientReadPlatformService, 
GroupReadPlatformService groupReadPlatformService,
-            SavingsProductReadPlatformService 
savingProductReadPlatformService, StaffReadPlatformService 
staffReadPlatformService,
-            SavingsDropdownReadPlatformService dropdownReadPlatformService, 
ChargeReadPlatformService chargeReadPlatformService,
-            EntityDatatableChecksReadService entityDatatableChecksReadService, 
ColumnValidator columnValidator,
             SavingsAccountAssembler savingAccountAssembler, PaginationHelper 
paginationHelper, DatabaseSpecificSQLGenerator sqlGenerator,
-            SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper) {
-        return new SavingsAccountReadPlatformServiceImpl(context, 
jdbcTemplate, clientReadPlatformService, groupReadPlatformService,
+            SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper, 
ColumnValidator columnValidator) {
+        return new SavingsAccountReadPlatformServiceImpl(context, 
jdbcTemplate, savingAccountAssembler, paginationHelper, columnValidator,
+                sqlGenerator, savingsAccountRepositoryWrapper);
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(SavingsAccountTemplateReadPlatformService.class)
+    public SavingsAccountTemplateReadPlatformService 
savingsAccountTemplateReadPlatformService(PlatformSecurityContext context,
+            JdbcTemplate jdbcTemplate, ClientReadPlatformService 
clientReadPlatformService,
+            GroupReadPlatformService groupReadPlatformService, 
SavingsProductReadPlatformService savingProductReadPlatformService,
+            StaffReadPlatformService staffReadPlatformService, 
SavingsDropdownReadPlatformService dropdownReadPlatformService,
+            ChargeReadPlatformService chargeReadPlatformService, 
EntityDatatableChecksReadService entityDatatableChecksReadService,
+            ColumnValidator columnValidator) {
+        return new SavingsAccountTemplateReadPlatformServiceImpl(context, 
jdbcTemplate, clientReadPlatformService, groupReadPlatformService,
                 savingProductReadPlatformService, staffReadPlatformService, 
dropdownReadPlatformService, chargeReadPlatformService,
-                entityDatatableChecksReadService, columnValidator, 
savingAccountAssembler, paginationHelper, sqlGenerator,
-                savingsAccountRepositoryWrapper);
+                entityDatatableChecksReadService, columnValidator);
     }
 
     @Bean
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableBusinessEventTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableBusinessEventTest.java
new file mode 100644
index 0000000000..13171e7d77
--- /dev/null
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableBusinessEventTest.java
@@ -0,0 +1,207 @@
+/**
+ * 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.infrastructure.dataqueries.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import 
org.apache.fineract.infrastructure.codes.service.CodeReadPlatformService;
+import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.ActionContext;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import 
org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
+import 
org.apache.fineract.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import 
org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
+import 
org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
+import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
+import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
+import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.datatable.DatatableEntryBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
+import org.apache.fineract.infrastructure.security.utils.DefaultSqlValidator;
+import org.apache.fineract.organisation.office.domain.Office;
+import org.apache.fineract.portfolio.search.service.SearchUtil;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.PreparedStatementCreator;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.support.KeyHolder;
+import org.springframework.jdbc.support.rowset.SqlRowSet;
+
+@ExtendWith(MockitoExtension.class)
+public class DatatableBusinessEventTest {
+
+    @Mock
+    private PlatformSecurityContext context;
+    @Mock
+    private BusinessEventNotifierService businessEventNotifierService;
+    @Mock
+    private JdbcTemplate jdbcTemplate;
+    @Mock
+    private GenericDataService genericDataService;
+    @Mock
+    private SearchUtil searchUtil;
+    @Mock
+    private DatabaseTypeResolver databaseTypeResolver;
+    @Mock
+    private DatabaseSpecificSQLGenerator sqlGenerator;
+    @Mock
+    private FineractPlatformTenantConnection tenantConnection;
+    @Mock
+    private DefaultSqlValidator sqlValidator;
+    @Mock
+    private FromJsonHelper fromJsonHelper;
+    @Mock
+    private DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer;
+    @Mock
+    private ConfigurationDomainService configurationDomainService;
+    @Mock
+    private CodeReadPlatformService codeReadPlatformService;
+    @Mock
+    private DataTableValidator dataTableValidator;
+    @Mock
+    private ColumnValidator columnValidator;
+    @Mock
+    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+    @Mock
+    private DatatableKeywordGenerator datatableKeywordGenerator;
+
+    @InjectMocks
+    private ReadWriteNonCoreDataServiceImpl underTest;
+
+    @Captor
+    private ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor;
+
+    private static String DATATABLE_NAME = "test_loan_data";
+
+    @BeforeEach
+    public void setUp() {
+        ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, 
"default", "Default", "Asia/Kolkata", null));
+        ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
+        ThreadLocalContextUtil.setBusinessDates(new 
HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.parse("2024-01-16"),
+                BusinessDateType.COB_DATE, LocalDate.parse("2024-01-15"))));
+
+        SqlRowSet sqlRS = Mockito.mock(SqlRowSet.class);
+        SqlRowSet sqlRSData = Mockito.mock(SqlRowSet.class);
+        doNothing().when(sqlValidator).validate(anyString());
+        when(jdbcTemplate.queryForRowSet(anyString(), 
anyString())).thenReturn(sqlRS);
+        when(jdbcTemplate.queryForRowSet(anyString())).thenReturn(sqlRSData);
+
+        when(sqlRS.next()).thenReturn(true).thenReturn(false);
+        when(sqlRS.getString("application_table_name")).thenReturn("m_loan");
+        when(sqlRSData.next()).thenReturn(true).thenReturn(false);
+        when(sqlRSData.getObject(anyString())).thenReturn(1L);
+
+        AppUser currentUser = Mockito.mock(AppUser.class);
+        Office office = Mockito.mock(Office.class);
+        when(context.authenticatedUser()).thenReturn(currentUser);
+        when(currentUser.getOffice()).thenReturn(office);
+        when(office.getHierarchy()).thenReturn(".");
+    }
+
+    @AfterEach
+    public void tearDown() {
+        ThreadLocalContextUtil.reset();
+    }
+
+    @Test
+    public void businessEventCreateNewDatatableEntryTest() {
+        when(jdbcTemplate.update(any(PreparedStatementCreator.class), 
any(KeyHolder.class))).thenReturn(1);
+
+        underTest.createNewDatatableEntry(DATATABLE_NAME, 1L, 
createJsonCommand("{}", 1L));
+
+        ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = 
verifyBusinessEvents(1);
+        verifyDatatableBusinessEvent(businessEventArgumentCaptor, 0, 1L);
+    }
+
+    @Test
+    public void businessEventUpdateDatatableEntryTest() {
+        List<Object> values = new ArrayList<>();
+        values.add(1L);
+        List<ResultsetRowData> result = new ArrayList<>();
+        result.add(ResultsetRowData.create(values));
+        when(genericDataService.fillResultsetRowData(anyString(), 
any())).thenReturn(result);
+
+        // ResultsetColumnHeaderData resultSet = 
Mockito.mock(ResultsetColumnHeaderData.class);
+        // when(searchUtil.getFiltered(columnHeaders,
+        // 
ResultsetColumnHeaderData::getIsColumnPrimaryKey)).thenReturn(resultSet);
+
+        underTest.updateDatatableEntryOneToMany(DATATABLE_NAME, 1L, 1L, 
createJsonCommand("{}", 1L));
+
+        ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = 
verifyBusinessEvents(1);
+        verifyDatatableBusinessEvent(businessEventArgumentCaptor, 0, 1L);
+    }
+
+    @Test
+    public void businessEventDeleteDatatableEntryTest() {
+
+        underTest.deleteDatatableEntry(DATATABLE_NAME, 1L, 1L, 
createJsonCommand("{}", 1L));
+
+        ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = 
verifyBusinessEvents(1);
+        verifyDatatableBusinessEvent(businessEventArgumentCaptor, 0, 1L);
+    }
+
+    @NotNull
+    private ArgumentCaptor<BusinessEvent<?>> verifyBusinessEvents(int 
expectedBusinessEvents) {
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<BusinessEvent<?>> businessEventArgumentCaptor = 
ArgumentCaptor.forClass(BusinessEvent.class);
+        verify(businessEventNotifierService, 
times(expectedBusinessEvents)).notifyPostBusinessEvent(businessEventArgumentCaptor.capture());
+        return businessEventArgumentCaptor;
+    }
+
+    private void verifyDatatableBusinessEvent(ArgumentCaptor<BusinessEvent<?>> 
businessEventArgumentCaptor, int index, Long entityId) {
+        assertTrue(businessEventArgumentCaptor.getAllValues().get(index) 
instanceof DatatableEntryBusinessEvent);
+        assertEquals(DATATABLE_NAME, ((DatatableEntryBusinessEvent) 
businessEventArgumentCaptor.getAllValues().get(index))
+                .getDatatableEntryDetails().getDatatableName());
+        assertEquals(entityId, ((DatatableEntryBusinessEvent) 
businessEventArgumentCaptor.getAllValues().get(index)).getAggregateRootId());
+    }
+
+    private JsonCommand createJsonCommand(final String jsonCommand, final Long 
resourceId) {
+        return new JsonCommand(null, jsonCommand, null, null, null, 
resourceId, null, null, null, null, null, null, null, null, null, null,
+                null, null);
+    }
+}
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
index 4a39f44540..dc36728b53 100644
--- 
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
@@ -40,8 +40,6 @@ public interface SavingsAccountReadPlatformService {
 
     SavingsAccountData retrieveOne(Long savingsId);
 
-    SavingsAccountData retrieveTemplate(Long clientId, Long groupId, Long 
productId, boolean staffInSelectedOfficeOnly);
-
     SavingsAccountTransactionData retrieveDepositTransactionTemplate(Long 
savingsId, DepositAccountType depositAccountType);
 
     Collection<SavingsAccountTransactionData> retrieveAllTransactions(Long 
savingsId, DepositAccountType depositAccountType);
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountTemplateReadPlatformService.java
 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountTemplateReadPlatformService.java
new file mode 100644
index 0000000000..d0f6233bef
--- /dev/null
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountTemplateReadPlatformService.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.savings.service;
+
+import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
+
+public interface SavingsAccountTemplateReadPlatformService {
+
+    SavingsAccountData retrieveTemplate(Long clientId, Long groupId, Long 
productId, boolean staffInSelectedOfficeOnly);
+
+}

Reply via email to