diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index a02cae75c..1a98be002 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -3864,7 +3864,7 @@
self/loans/{loanId}/guarantors |
|
Guarantors list |
- |
+ |
|
@@ -3875,6 +3875,7 @@
+
| Run Report |
self/runreports/{reportName} |
@@ -3904,6 +3905,30 @@
self/surveys/scorecards/clients/{clientId} |
|
Survey Scorecards |
+ |
+ |
+
+
+ | Link/Delink accounts to pocket |
+ self/pockets?command=linkAccounts |
+ Link accounts to pocket |
+ |
+ |
+ |
+
+
+ |
+ self/pockets?command=delinkAccounts |
+ Delink accounts from pocket |
+ |
+ |
+ |
+
+
+ |
+ self/pockets |
+ |
+ Retrieve accounts linked to pocket |
|
|
@@ -50691,6 +50716,102 @@ GET https://DomainName/api/v1/self/runreports/{reportName}
+
+
+
+
+
Link/delink accounts to/from pocket
+
Pockets behave as favourites. An user can link his/her Loan, Savings and Share accounts to pocket for faster access. In a similar way linked accounts can be delinked from the pocket.
+
Link accounts to pocket
+
Example Requests:
+
self/pockets?command=linkAccounts
+
+
+ POST https://DomainName/api/v1/self/pockets?command=linkAccounts
+ POST self/pockets?command=linkAccounts
+Content-Type: application/json
+Request Body:
+
+{
+ "accountsDetail":[
+ {
+ "accountId":"11",
+ "accountType":"LOAN"
+ },
+ {
+ "accountId":"2",
+ "accountType":"SAVINGS"
+ }
+ ]
+}
+
+
+{
+ "resourceId": 6
+}
+
+
+
+
+
+
+
+
Delink accounts from pocket
+
Example Requests:
+
self/pockets?command=delinkAccounts
+
+
+ POST https://DomainName/api/v1/self/pockets?command=delinkAccounts
+ POST self/pockets?command=delinkAccounts
+Content-Type: application/json
+Request Body:
+
+{ "pocketAccountMappingIds" :[8,9]
+}
+
+
+{
+ "resourceId":10
+}
+
+
+
+
+
+
+
+
Retrieve accounts linked to pocket
+
All linked loan
+
Example Requests:
+
self/pockets
+
+
+ POST https://DomainName/api/v1/self/pockets
+
+{
+ "loanAccounts": [
+ {
+ "pocketId": 6,
+ "accountId": 11,
+ "accountType": 2,
+ "accountNumber": "000000011",
+ "id": 10
+ }
+ ],
+ "savingsAccounts": [
+ {
+ "pocketId": 6,
+ "accountId": 2,
+ "accountType": 3,
+ "accountNumber": "000000002",
+ "id": 11
+ }
+ ],
+ "shareAccounts": []
+}
+
+
+
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index ae53bb78c..eb3fe6dce 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -23,6 +23,7 @@ import org.apache.fineract.infrastructure.accountnumberformat.service.AccountNum
import org.apache.fineract.portfolio.client.api.ClientApiConstants;
import org.apache.fineract.portfolio.paymenttype.api.PaymentTypeApiResourceConstants;
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
+import org.apache.fineract.portfolio.self.pockets.api.PocketApiConstants;
import org.apache.fineract.useradministration.api.PasswordPreferencesApiConstants;
public class CommandWrapperBuilder {
@@ -3132,4 +3133,18 @@ public class CommandWrapperBuilder {
this.href = "/twofactor/configure";
return this;
}
+
+ public CommandWrapperBuilder linkAccountsToPocket() {
+ this.actionName = PocketApiConstants.linkAccountsActionName;
+ this.entityName = PocketApiConstants.pocketEntityName;
+ this.href = "/self/pocket?command="+PocketApiConstants.linkAccountsToPocketCommandParam;
+ return this;
+ }
+
+ public CommandWrapperBuilder delinkAccountsFromPocket() {
+ this.actionName = PocketApiConstants.delinkAccountsActionName;
+ this.entityName = PocketApiConstants.pocketEntityName;
+ this.href = "/self/pocket?command="+PocketApiConstants.delinkAccountsFromPocketCommandParam;
+ return this;
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
index e205fbf19..2fd6c62ea 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java
@@ -134,4 +134,7 @@ public interface LoanReadPlatformService {
LoanAccountData retrieveLoanByLoanAccount(String loanAccountNumber);
Long retrieveLoanIdByAccountNumber(String loanAccountNumber);
+
+ String retrieveAccountNumberByAccountId(Long accountId);
+
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index e2464d83b..20230d6a4 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -2209,4 +2209,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
}
+ public String retrieveAccountNumberByAccountId(Long accountId) {
+ try {
+ final String sql = "select loan.account_no from m_loan loan where loan.id = ?";
+ return this.jdbcTemplate.queryForObject(sql, new Object[] { accountId }, String.class);
+ } catch (final EmptyResultDataAccessException e) {
+ throw new LoanNotFoundException(accountId);
+ }
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
index adac1919b..2593297b5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
@@ -60,4 +60,6 @@ public interface SavingsAccountReadPlatformService {
List retrieveSavingsIdsPendingEscheat(LocalDate tenantLocalDate);
boolean isAccountBelongsToClient(final Long clientId, final Long accountId, final DepositAccountType depositAccountType, final String currencyCode) ;
+ String retrieveAccountNumberByAccountId(Long accountId);
+
}
\ No newline at end of file
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 6bb4fd1ce..b0ba652c7 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
@@ -1248,4 +1248,14 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
* return SavingsAccountAnnualFeeData.instance(id, accountNo,
* annualFeeNextDueDate); } }
*/
+
+ @Override
+ public String retrieveAccountNumberByAccountId(Long accountId) {
+ try {
+ final String sql = "select s.account_no from m_savings_account s where s.id = ?";
+ return this.jdbcTemplate.queryForObject(sql, new Object[] { accountId }, String.class);
+ } catch (final EmptyResultDataAccessException e) {
+ throw new SavingsAccountNotFoundException(accountId);
+ }
+ }
}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiConstants.java
new file mode 100644
index 000000000..11df8cc21
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiConstants.java
@@ -0,0 +1,49 @@
+/**
+ * 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.self.pockets.api;
+
+public interface PocketApiConstants {
+
+ String pocketsResourceName = "pockets";
+ String linkAccountsToPocketCommandParam = "linkAccounts";
+ String delinkAccountsFromPocketCommandParam = "delinkAccounts";
+
+ String linkAccountsActionName = "LINK_ACCOUNT_TO";
+ String pocketEntityName = "POCKET";
+ String delinkAccountsActionName = "DELINK_ACCOUNT_FROM";
+
+ String accountIdParamName = "accountId";
+ String accountTypeParamName = "accountType";
+ String accountsDetail = "accountsDetail";
+ String pocketAccountMappingList = "pocketAccountMappingIds";
+ String pocketAccountMappingId = "pocketAccountMappingId";
+
+ String dataValidationMessage = "validation.msg.validation.errors.exist";
+ String validationErrorMessage = "Validation errors exist.";
+ String pocketNotFoundException = "error.msg.pocket.not.found";
+ String pocketNotFoundErrorMessage = "Pocket not found.";
+ String mappingIdNotLinkedToPocketException = "mapping.id.not.linked.to.pocket.exception";
+ String mappingIdNotLinkedToPocketErrorMessage = "Mapping Id is not linked to Pocket.";
+ String uniqueConstraintName = "m_pocket_account_unique_mapping";
+ String duplicateMappingException = "error.msg.one.or.more.accounts.are.already.mapped.to.pocket.";
+ String duplicateMappingExceptionMessage = "One or more accounts are already mapped to pocket.";
+ String unknownDataIntegrityException = "error.msg.unknown.data.integrity.issue";
+ String unknownDataIntegrityExceptionMessage = "Unknown data integrity issue with resource.";
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResource.java
new file mode 100644
index 000000000..1d39cc2b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResource.java
@@ -0,0 +1,99 @@
+/**
+ * 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.self.pockets.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.portfolio.self.pockets.service.PocketAccountMappingReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/self/pockets")
+@Component
+@Scope("singleton")
+public class PocketApiResource {
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+ @SuppressWarnings("rawtypes")
+ private final DefaultToApiJsonSerializer toApiJsonSerializer;
+ private final PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService;
+
+ @SuppressWarnings("rawtypes")
+ @Autowired
+ public PocketApiResource(PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+ final DefaultToApiJsonSerializer toApiJsonSerializer,
+ final PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService) {
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ this.toApiJsonSerializer = toApiJsonSerializer;
+ this.pocketAccountMappingReadPlatformService = pocketAccountMappingReadPlatformService;
+ }
+
+ @POST
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String handleCommands(@QueryParam("command") final String commandParam, @Context final UriInfo uriInfo,
+ final String apiRequestBodyAsJson) {
+
+ final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
+
+ CommandProcessingResult result = null;
+
+ if (is(commandParam, PocketApiConstants.linkAccountsToPocketCommandParam)) {
+ final CommandWrapper commandRequest = builder.linkAccountsToPocket().build();
+ result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+ } else if (is(commandParam, PocketApiConstants.delinkAccountsFromPocketCommandParam)) {
+ final CommandWrapper commandRequest = builder.delinkAccountsFromPocket().build();
+ result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+ }
+
+ if (result == null) {
+ throw new UnrecognizedQueryParamException("command", commandParam);
+ }
+
+ return this.toApiJsonSerializer.serialize(result);
+ }
+
+ @GET
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveAll() {
+ return this.toApiJsonSerializer.serialize(this.pocketAccountMappingReadPlatformService.retrieveAll());
+ }
+
+ private boolean is(final String commandParam, final String commandValue) {
+ return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketAccountMappingData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketAccountMappingData.java
new file mode 100644
index 000000000..34b984f63
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketAccountMappingData.java
@@ -0,0 +1,45 @@
+/**
+ * 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.self.pockets.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMapping;
+
+@SuppressWarnings("unused")
+public class PocketAccountMappingData {
+
+ private final Collection loanAccounts;
+ private final Collection savingsAccounts;
+ private final Collection shareAccounts;
+
+ private PocketAccountMappingData(final Collection loanAccounts, Collection savingsAccounts, final Collection shareAccounts ) {
+ this.loanAccounts = loanAccounts;
+ this.savingsAccounts = savingsAccounts;
+ this.shareAccounts = shareAccounts;
+ }
+
+ public static PocketAccountMappingData instance(final Collection loanAccounts, final Collection savingsAccounts, final Collection shareAccounts) {
+
+ return new PocketAccountMappingData(loanAccounts, savingsAccounts, shareAccounts);
+
+ }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketDataValidator.java
new file mode 100644
index 000000000..29d762420
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketDataValidator.java
@@ -0,0 +1,140 @@
+/**
+ * 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.self.pockets.data;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.self.pockets.api.PocketApiConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Service
+public class PocketDataValidator {
+ private final Set linkingAccountsSupportedParameters = new HashSet<>(
+ Arrays.asList(PocketApiConstants.accountIdParamName, PocketApiConstants.accountTypeParamName,
+ PocketApiConstants.accountsDetail));
+
+ private final Set delinkingAccountsSupportedParameters = new HashSet<>(
+ Arrays.asList(PocketApiConstants.pocketAccountMappingList));
+
+ private final FromJsonHelper fromApiJsonHelper;
+
+ @Autowired
+ public PocketDataValidator(FromJsonHelper fromApiJsonHelper) {
+ this.fromApiJsonHelper = fromApiJsonHelper;
+ }
+
+ public void validateForLinkingAccounts(final String json) {
+ if (StringUtils.isBlank(json)) {
+ throw new InvalidJsonException();
+ }
+
+ final Type typeOfMap = new TypeToken