From 2d6a19a99814f85fb07c33bdd630da4b430ce355 Mon Sep 17 00:00:00 2001 From: ShruthiRajaram Date: Tue, 31 Jul 2018 19:01:04 +0530 Subject: [PATCH] FINERACT:628 API support for self share accounts --- api-docs/apiLive.htm | 907 ++++++++++++++++++ .../constants/ShareAccountApiConstants.java | 4 +- .../api/SelfShareAccountsApiResource.java | 167 ++++ .../data/SelfShareAccountsDataValidator.java | 75 ++ ...hareAccountsMapperReadPlatformService.java | 26 + ...AccountsMapperReadPlatformServiceImpl.java | 51 + 6 files changed, 1229 insertions(+), 1 deletion(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/api/SelfShareAccountsApiResource.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/data/SelfShareAccountsDataValidator.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformServiceImpl.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 9a87171ccca..c6f966ff751 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -3697,6 +3697,30 @@

Self Service

+ + Share Accounts + self/shareaccounts/template?clientId=?productId=? + + Share Accounts Template + + + + + + + self/shareaccounts + Submit New Share Application + + + + + + + self/shareaccounts/{accountId} + + Retrieve a Share Application/Account + + Own Account Transfers self/accounttransfers/template @@ -47487,6 +47511,889 @@

Third Party Account Transfer

+ +   +
+
+

Retrieve Share Account Template:

+

This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of: +

    +
  • Field Defaults
  • +
  • Allowed Value Lists
  • +
+

+

Arguments

+
+
clientId
+
Integer mandatory
+
productId
+
Integer optional
+
If entered, productId, productName and selectedProduct fields are returned.
+
+

Example Requests:

+
self/shareaccounts/template?clientId=14
+

+
self/shareaccounts/template?clientId=14&productId=3
+
+
+ GET https://Domain Name/api/v1/self/shareaccounts/template?clientId={clientId} + +{ + "clientId": 14, + "productOptions": [ + { + "id": 3, + "name": "SP", + "shortName": "SP", + "totalShares": 1000 + }, + { + "id": 4, + "name": "Product1", + "shortName": "test", + "totalShares": 1000 + } + ], + "chargeOptions": [ + { + "id": 2, + "name": "Activation fee", + "active": true, + "penalty": false, + "currency": { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + }, + "amount": 2, + "chargeTimeType": { + "id": 14, + "code": "chargeTimeType.sharespurchase", + "value": "Share Purchase" + }, + "chargeAppliesTo": { + "id": 4, + "code": "chargeAppliesTo.shares", + "value": "Shares" + }, + "chargeCalculationType": { + "id": 1, + "code": "chargeCalculationType.flat", + "value": "Flat" + }, + "chargePaymentMode": { + "id": 0, + "code": "chargepaymentmode.regular", + "value": "Regular" + } + } + ] +} + +
+ +
+ GET https://Domain Name/api/v1/self/shareaccounts/template?clientId={clientId}&productId={productId} + +{ + "clientId": 14, + "productOptions": [ + { + "id": 3, + "name": "SP", + "shortName": "SP", + "description": "SP1", + "currency": { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "inMultiplesOf": 1, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + }, + "totalShares": 1000, + "totalSharesIssued": 900, + "unitPrice": 1, + "shareCapital": 900, + "minimumShares": 8, + "nominalShares": 500, + "maximumShares": 500, + "marketPrice": [], + "charges": [], + "allowDividendCalculationForInactiveClients": false, + "lockinPeriod": 50, + "lockPeriodTypeEnum": { + "id": 0, + "code": "shares.lockin.sharePeriodFrequencyType.days", + "value": "Days" + }, + "minimumActivePeriod": 10, + "minimumActivePeriodForDividendsTypeEnum": { + "id": 0, + "code": "shares.minimumactive.sharePeriodFrequencyType.days", + "value": "Days" + }, + "accountingRule": { + "id": 2, + "code": "accountingRuleType.cash", + "value": "CASH BASED" + }, + "accountingMappings": { + "shareReferenceId": { + "id": 32, + "name": "Cash In Hand", + "glCode": "20301" + }, + "incomeFromFeeAccountId": { + "id": 40, + "name": "Other Operating Income", + "glCode": "30105" + }, + "shareEquityId": { + "id": 56, + "name": "Share Equity", + "glCode": "00098" + }, + "shareSuspenseId": { + "id": 2, + "name": "Overpayment Liability", + "glCode": "10200" + } + }, + "currencyOptions": [ + { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + } + ], + "chargeOptions": [ + { + "id": 2, + "name": "Activation fee", + "active": true, + "penalty": false, + "currency": { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + }, + "amount": 2, + "chargeTimeType": { + "id": 14, + "code": "chargeTimeType.sharespurchase", + "value": "Share Purchase" + }, + "chargeAppliesTo": { + "id": 4, + "code": "chargeAppliesTo.shares", + "value": "Shares" + }, + "chargeCalculationType": { + "id": 1, + "code": "chargeCalculationType.flat", + "value": "Flat" + }, + "chargePaymentMode": { + "id": 0, + "code": "chargepaymentmode.regular", + "value": "Regular" + } + } + ], + "minimumActivePeriodFrequencyTypeOptions": [ + { + "id": 0, + "code": "shares.lockin.sharePeriodFrequencyType.days", + "value": "Days" + } + ], + "lockinPeriodFrequencyTypeOptions": [ + { + "id": 0, + "code": "shares.lockin.sharePeriodFrequencyType.days", + "value": "Days" + }, + { + "id": 1, + "code": "shares.lockin.sharePeriodFrequencyType.weeks", + "value": "Weeks" + }, + { + "id": 2, + "code": "shares.lockin.sharePeriodFrequencyType.months", + "value": "Months" + }, + { + "id": 3, + "code": "shares.lockin.sharePeriodFrequencyType.years", + "value": "Years" + } + ], + "accountingMappingOptions": { + "liabilityAccountOptions": [ + { + "id": 30, + "name": "Recurring Deposits", + "parentId": 1, + "glCode": "10104", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 2, + "code": "accountType.liability", + "value": "LIABILITY" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Recurring Deposits", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 2, + "name": "Overpayment Liability", + "glCode": "10200", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 2, + "code": "accountType.liability", + "value": "LIABILITY" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "Overpayment Liability", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 55, + "name": "Liability Transfer (Temp)", + "glCode": "220004-Temp", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 2, + "code": "accountType.liability", + "value": "LIABILITY" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "description": "Temporary Liability account to track Account Transfers", + "nameDecorated": "Liability Transfer (Temp)", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + } + ], + "assetAccountOptions": [ + { + "id": 31, + "name": "Furniture and Fixtures", + "parentId": 8, + "glCode": "20101", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 1, + "code": "accountType.asset", + "value": "ASSET" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Furniture and Fixtures", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 32, + "name": "Cash In Hand", + "parentId": 10, + "glCode": "20301", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 1, + "code": "accountType.asset", + "value": "ASSET" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Cash In Hand", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 33, + "name": "Petty Cash", + "parentId": 10, + "glCode": "20302", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 1, + "code": "accountType.asset", + "value": "ASSET" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Petty Cash", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 34, + "name": "Loans to Customers", + "parentId": 12, + "glCode": "20501", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 1, + "code": "accountType.asset", + "value": "ASSET" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Loans to Customers", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 35, + "name": "Current Account Overdrafts", + "parentId": 12, + "glCode": "20502", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 1, + "code": "accountType.asset", + "value": "ASSET" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Current Account Overdrafts", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 17, + "name": "Suspense Account", + "glCode": "9999", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 1, + "code": "accountType.asset", + "value": "ASSET" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "Suspense Account", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + } + ], + "incomeAccountOptions": [ + { + "id": 54, + "name": "Loan Recovery (Temp)", + "glCode": "220002-Temp", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 4, + "code": "accountType.income", + "value": "INCOME" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "description": "Temporary account to track income from Loan recovery", + "nameDecorated": "Loan Recovery (Temp)", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 36, + "name": "Fees and Charges", + "parentId": 18, + "glCode": "30101", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 4, + "code": "accountType.income", + "value": "INCOME" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Fees and Charges", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 37, + "name": "Penalties", + "parentId": 18, + "glCode": "30102", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 4, + "code": "accountType.income", + "value": "INCOME" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Penalties", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 38, + "name": "Interest Received from Borrowers", + "parentId": 18, + "glCode": "30103", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 4, + "code": "accountType.income", + "value": "INCOME" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Interest Received from Borrowers", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 39, + "name": "Insurance Charges", + "parentId": 18, + "glCode": "30104", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 4, + "code": "accountType.income", + "value": "INCOME" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Insurance Charges", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + }, + { + "id": 40, + "name": "Other Operating Income", + "parentId": 18, + "glCode": "30105", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 4, + "code": "accountType.income", + "value": "INCOME" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Other Operating Income", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + } + ], + "equityAccountOptions": [ + { + "id": 56, + "name": "Share Equity", + "parentId": 25, + "glCode": "00098", + "disabled": false, + "manualEntriesAllowed": true, + "type": { + "id": 3, + "code": "accountType.equity", + "value": "EQUITY" + }, + "usage": { + "id": 1, + "code": "accountUsage.detail", + "value": "DETAIL" + }, + "nameDecorated": "....Share Equity", + "tagId": { + "id": 0, + "active": false, + "mandatory": false + } + } + ] + } + } + ], + "chargeOptions": [ + { + "id": 2, + "name": "Activation fee", + "active": true, + "penalty": false, + "currency": { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + }, + "amount": 2, + "chargeTimeType": { + "id": 14, + "code": "chargeTimeType.sharespurchase", + "value": "Share Purchase" + }, + "chargeAppliesTo": { + "id": 4, + "code": "chargeAppliesTo.shares", + "value": "Shares" + }, + "chargeCalculationType": { + "id": 1, + "code": "chargeCalculationType.flat", + "value": "Flat" + }, + "chargePaymentMode": { + "id": 0, + "code": "chargepaymentmode.regular", + "value": "Regular" + } + } + ] +} + +
+
+ +  +
+
+

Submit new share application

+
+ + + +
Mandatory Fields
clientId, productId, submittedDate, savingsAccountId, requestedShares, applicationDate
+
+ + + +
Optional Fields
accountNo, externalId
+
+ + + + +
Inherited from Product (if not provided)
minimumActivePeriod, minimumActivePeriodFrequencyType, lockinPeriodFrequency, lockinPeriodFrequencyType
+
+
+

Minimal request: accountNo auto generated, remaining details inherited from Share product.

+ POST https://Domain Name/api/v1/self/shareaccounts + POST Share Account +Content-Type: application/json +Request Body: +{ + "productId":3, + "unitPrice":1, + "requestedShares":500, + "submittedDate":"31 July 2018", + "savingsAccountId":2, + "applicationDate":"31 July 2018", + "locale":"en", + "dateFormat":"dd MMMM yyyy", + "charges":[ + { + "chargeId":2, + "amount":2 + } + ], + "clientId":"14" +} + + +{ + "resourceId": 12 +} + +
+
+ +  +
+
+

Retrieve a share application/account:

+ +

Example Requests :

+
self/shareaccounts/12
+
+
+ GET https://DomainName/api/v1/self/shareaccounts/{accountId} + +{ + "id": 12, + "accountNo": "000000012", + "savingsAccountNumber": "000000002", + "clientId": 14, + "clientName": "Bheem", + "productId": 3, + "productName": "SP", + "status": { + "id": 100, + "code": "shareAccountStatusType.submitted.and.pending.approval", + "value": "Submitted and pending approval", + "submittedAndPendingApproval": true, + "approved": false, + "rejected": false, + "active": false, + "closed": false + }, + "timeline": { + "submittedOnDate": [ + 2018, + 7, + 31 + ], + "submittedByUsername": "self", + "submittedByFirstname": "self", + "submittedByLastname": "self" + }, + "currency": { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "inMultiplesOf": 1, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + }, + "summary": { + "id": 12, + "accountNo": "000000012", + "totalApprovedShares": 0, + "totalPendingForApprovalShares": 500, + "productId": 3, + "productName": "SP", + "status": { + "id": 100, + "code": "shareAccountStatusType.submitted.and.pending.approval", + "value": "Submitted and pending approval", + "submittedAndPendingApproval": true, + "approved": false, + "rejected": false, + "active": false, + "closed": false + }, + "currency": { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "inMultiplesOf": 1, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + }, + "timeline": { + "submittedOnDate": [ + 2018, + 7, + 31 + ], + "submittedByUsername": "self", + "submittedByFirstname": "self", + "submittedByLastname": "self" + } + }, + "purchasedShares": [ + { + "id": 13, + "accountId": 12, + "purchasedDate": [ + 2018, + 7, + 31 + ], + "numberOfShares": 500, + "purchasedPrice": 1, + "status": { + "id": 100, + "code": "purchasedSharesStatusType.applied", + "value": "Pending Approval" + }, + "type": { + "id": 500, + "code": "purchasedSharesType.purchased", + "value": "Purchase" + }, + "amount": 502, + "chargeAmount": 2, + "amountPaid": 500 + } + ], + "savingsAccountId": 2, + "currentMarketPrice": 1, + "lockPeriodTypeEnum": { + "id": 4, + "code": "sharePeriodFrequencyType.invalid", + "value": "Invalid" + }, + "minimumActivePeriodTypeEnum": { + "id": 4, + "code": "sharePeriodFrequencyType.invalid", + "value": "Invalid" + }, + "allowDividendCalculationForInactiveClients": false, + "charges": [ + { + "id": 9, + "chargeId": 2, + "accountId": 12, + "name": "Activation fee", + "chargeTimeType": { + "id": 14, + "code": "chargeTimeType.sharespurchase", + "value": "Share Purchase" + }, + "chargeCalculationType": { + "id": 1, + "code": "chargeCalculationType.flat", + "value": "Flat" + }, + "percentage": 0, + "amountPercentageAppliedTo": 0, + "currency": { + "code": "USD", + "name": "US Dollar", + "decimalPlaces": 2, + "displaySymbol": "$", + "nameCode": "currency.USD", + "displayLabel": "US Dollar ($)" + }, + "amount": 2, + "amountPaid": 0, + "amountWaived": 0, + "amountWrittenOff": 0, + "amountOutstanding": 2, + "amountOrPercentage": 2, + "isActive": true + } + ], + "dividends": [] +} + +
+
+ diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/ShareAccountApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/ShareAccountApiConstants.java index 7ab98a1ca44..04aeb749c72 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/ShareAccountApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accounts/constants/ShareAccountApiConstants.java @@ -95,7 +95,9 @@ public interface ShareAccountApiConstants { public String additionalshares_paramname = "additionalshares" ; - public String closeddate_paramname = "closedDate" ; + public String closeddate_paramname = "closedDate"; + + public static final String shareEntityType = "share"; Set supportedParameters = new HashSet<>(Arrays.asList(locale_paramname, dateformat_paramname, id_paramname,clientid_paramname, productid_paramname, submitteddate_paramname,approveddate_paramname, externalid_paramname, currency_paramname, digitsafterdecimal_paramname, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/api/SelfShareAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/api/SelfShareAccountsApiResource.java new file mode 100644 index 00000000000..11adb112184 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/api/SelfShareAccountsApiResource.java @@ -0,0 +1,167 @@ +/** + * 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.shareaccounts.api; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +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.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.accounts.api.AccountsApiResource; +import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants; +import org.apache.fineract.portfolio.accounts.data.AccountData; +import org.apache.fineract.portfolio.accounts.exceptions.ShareAccountNotFoundException; +import org.apache.fineract.portfolio.charge.data.ChargeData; +import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService; +import org.apache.fineract.portfolio.client.exception.ClientNotFoundException; +import org.apache.fineract.portfolio.products.data.ProductData; +import org.apache.fineract.portfolio.products.service.ProductReadPlatformService; +import org.apache.fineract.portfolio.self.client.service.AppuserClientMapperReadService; +import org.apache.fineract.portfolio.self.shareaccounts.data.SelfShareAccountsDataValidator; +import org.apache.fineract.portfolio.self.shareaccounts.service.AppUserShareAccountsMapperReadPlatformService; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountData; +import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformService; +import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/self/shareaccounts") +@Component +@Scope("singleton") +public class SelfShareAccountsApiResource { + + private final PlatformSecurityContext context; + private final AccountsApiResource accountsApiResource; + private final ShareAccountReadPlatformService readPlatformService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final AppuserClientMapperReadService appuserClientMapperReadService; + private final SelfShareAccountsDataValidator selfShareAccountsDataValidator; + private final ProductReadPlatformService productReadPlatformService; + private final ChargeReadPlatformService chargeReadPlatformService; + private final AppUserShareAccountsMapperReadPlatformService appUserShareAccountsMapperReadPlatformService; + + @Autowired + public SelfShareAccountsApiResource(final PlatformSecurityContext context, + final AccountsApiResource accountsApiResource, final ShareAccountReadPlatformService readPlatformService, + final DefaultToApiJsonSerializer toApiJsonSerializer, + final ApiRequestParameterHelper apiRequestParameterHelper, + final AppuserClientMapperReadService appuserClientMapperReadService, + final SelfShareAccountsDataValidator selfShareAccountsDataValidator, + final ProductReadPlatformService productReadPlatformService, + final ChargeReadPlatformService chargeReadPlatformService, + final AppUserShareAccountsMapperReadPlatformService appUserShareAccountsMapperReadPlatformService) { + this.context = context; + this.accountsApiResource = accountsApiResource; + this.readPlatformService = readPlatformService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.selfShareAccountsDataValidator = selfShareAccountsDataValidator; + this.appuserClientMapperReadService = appuserClientMapperReadService; + this.productReadPlatformService = productReadPlatformService; + this.chargeReadPlatformService = chargeReadPlatformService; + this.appUserShareAccountsMapperReadPlatformService = appUserShareAccountsMapperReadPlatformService; + + } + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String template(@QueryParam("clientId") final Long clientId, @QueryParam("productId") final Long productId, + @Context final UriInfo uriInfo) { + + validateAppuserClientsMapping(clientId); + + Collection productOptions = new ArrayList(); + if (productId != null) { + final boolean includeTemplate = true; + productOptions.add(productReadPlatformService.retrieveOne(productId, includeTemplate)); + } else { + productOptions = productReadPlatformService.retrieveAllForLookup(); + } + + String clientName = null; + + final Collection chargeOptions = this.chargeReadPlatformService.retrieveSharesApplicableCharges(); + final ShareAccountData accountData = new ShareAccountData(clientId, clientName, productOptions, chargeOptions); + + return this.toApiJsonSerializer.serialize(accountData); + + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String createAccount(final String apiRequestBodyAsJson) { + HashMap attr = this.selfShareAccountsDataValidator + .validateShareAccountApplication(apiRequestBodyAsJson); + final Long clientId = (Long) attr.get(ShareAccountApiConstants.clientid_paramname); + validateAppuserClientsMapping(clientId); + String accountType = ShareAccountApiConstants.shareEntityType; + return this.accountsApiResource.createAccount(accountType, apiRequestBodyAsJson); + } + + @GET + @Path("{accountId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveShareAccount(@PathParam("accountId") final Long accountId, @Context final UriInfo uriInfo) { + validateAppuserShareAccountMapping(accountId); + final boolean includeTemplate = false; + AccountData accountData = this.readPlatformService.retrieveOne(accountId, includeTemplate); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper + .process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, accountData, readPlatformService.getResponseDataParams()); + } + + private void validateAppuserShareAccountMapping(final Long accountId) { + AppUser user = this.context.authenticatedUser(); + final boolean isMapped = this.appUserShareAccountsMapperReadPlatformService + .isShareAccountsMappedToUser(accountId, user.getId()); + if (!isMapped) { + throw new ShareAccountNotFoundException(accountId); + } + } + + private void validateAppuserClientsMapping(final Long clientId) { + AppUser user = this.context.authenticatedUser(); + final boolean mappedClientId = this.appuserClientMapperReadService.isClientMappedToUser(clientId, user.getId()); + if (!mappedClientId) { + throw new ClientNotFoundException(clientId); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/data/SelfShareAccountsDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/data/SelfShareAccountsDataValidator.java new file mode 100644 index 00000000000..c762ecb750b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/data/SelfShareAccountsDataValidator.java @@ -0,0 +1,75 @@ +/** + * 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.shareaccounts.data; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; + +@Component +public class SelfShareAccountsDataValidator { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public SelfShareAccountsDataValidator(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public HashMap validateShareAccountApplication(final String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(ShareAccountApiConstants.shareEntityType); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final String clientId = this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.clientid_paramname, + element); + baseDataValidator.reset().parameter(ShareAccountApiConstants.clientid_paramname).value(clientId).notNull() + .longGreaterThanZero(); + + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + HashMap retAttr = new HashMap<>(); + retAttr.put(ShareAccountApiConstants.clientid_paramname, Long.parseLong(clientId)); + + return retAttr; + + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformService.java new file mode 100644 index 00000000000..3e7edbdfbc4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformService.java @@ -0,0 +1,26 @@ +/** + * 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.shareaccounts.service; + +public interface AppUserShareAccountsMapperReadPlatformService { + + public Boolean isShareAccountsMappedToUser(Long accountId, Long appUserId); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformServiceImpl.java new file mode 100644 index 00000000000..eb464f1a2c1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/shareaccounts/service/AppUserShareAccountsMapperReadPlatformServiceImpl.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.self.shareaccounts.service; + +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +@Service +public class AppUserShareAccountsMapperReadPlatformServiceImpl + implements AppUserShareAccountsMapperReadPlatformService { + + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public AppUserShareAccountsMapperReadPlatformServiceImpl( + final RoutingDataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public Boolean isShareAccountsMappedToUser(Long accountId, Long appUserId) { + return this.jdbcTemplate + .queryForObject( + "select case when (count(*) > 0) then true else false end " + + " from m_selfservice_user_client_mapping as m " + + " left join m_share_account as shares on shares.client_id = m.client_id " + + " where shares.id = ? and m.appuser_id = ? ", + new Object[] { accountId, appUserId }, Boolean.class); + } + +}