Merge pull request #1 from apache/develop

sync from original repo
This commit is contained in:
hungnguyen2712
2019-03-14 10:58:17 +07:00
committed by GitHub
59 changed files with 1831 additions and 48 deletions
+19
View File
@@ -0,0 +1,19 @@
## Description
Describe the changes made and why they were made. Ignore if these details are present on the associated Jira ticket
## Checklist
Please make sure these boxes are checked before submitting your pull request - thanks!
- [ ] Commit message starts with the issue number from https://issues.apache.org/jira/projects/FINERACT/. Ex: FINERACT-646 Pockets API.
- [ ] Coding conventions at https://cwiki.apache.org/confluence/display/FINERACT/Coding+Conventions have been followed.
- [ ] API documentation at https://github.com/apache/fineract/blob/develop/api-docs/apiLive.htm has been updated with details of any API changes.
- [ ] Integration tests have been created/updated for verifying the changes made.
- [ ] All Integrations tests are passing with the new commit.
- [ ] PR contains a single commit (If you have multiple commits, please squash them into a single commit).
Our guidelines for code reviews is at https://cwiki.apache.org/confluence/display/FINERACT/Code+Review+Guide
+6
View File
@@ -4,3 +4,9 @@
*.ear
.gradle
build
*.iml
*.ipr
*.iws
*.DS_Store
.idea
gradle
+19 -2
View File
@@ -20,6 +20,18 @@ language: java
jdk:
- openjdk8
services:
- mysql
# Hardcoding system time as a temporary fix for running buggy test cases
before_install:
- echo "USE mysql;\nUPDATE user SET password=PASSWORD('mysql') WHERE user='root';\nFLUSH PRIVILEGES;\n" | mysql -u root
- mysql -u root -pmysql -e 'CREATE DATABASE IF NOT EXISTS `mifosplatform-tenants`;'
- mysql -u root -pmysql -e 'CREATE DATABASE IF NOT EXISTS `mifostenant-default`;'
- export TZ=Asia/Kolkata
- sudo service ntp stop
- sudo date --set="23 February 2019 01:02:03"
# https://docs.travis-ci.com/user/languages/java/#caching
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
@@ -30,9 +42,14 @@ cache:
- $HOME/.gradle/wrapper/
- $HOME/.m2
# NOTE: The --info, while quite a bit more verbose, is VERY useful to understand failures on Travis,
# NOTE: We used to run with --info, which is quite a bit more verbose, but is VERY useful to understand failures on Travis,
# where you do not have access to any files like build/reports/tests/index.html, only the Console.
# @see http://mrhaki.blogspot.ch/2013/05/gradle-goodness-show-more-information.html
# @see http://forums.gradle.org/gradle/topics/whats_new_in_gradle_1_1_test_logging for alternative
# https://jira.apache.org/jira/browse/FINERACT-732 removed that again, because it made Travis CI fail.
script:
- ./gradlew --info licenseMain licenseTest licenseIntegrationTest test
- date
- ./gradlew --console=plain licenseMain licenseTest licenseIntegrationTest test
- ./gradlew --console=plain migrateTenantListDB -PdbName=mifosplatform-tenants
- ./gradlew --console=plain migrateTenantDB -PdbName=mifostenant-default
- ./gradlew --console=plain clean integrationTest
+18 -10
View File
@@ -1,7 +1,6 @@
Apache Fineract: A Platform for Microfinance [![Build Status](https://travis-ci.org/apache/fineract.svg?branch=develop)](https://travis-ci.org/apache/fineract)
============
The next evolution of Apache Fineract focuses on being faster, lighter and cheaper to change (than the existing Mifos) so that it is more responsive to the needs of Microfinance Institutions and Integrators.
Fineract is a mature platform with open APIs that provides a reliable, robust, and affordable core banking solution for financial institutions offering services to the world’s 2 billion underbanked and unbanked.
Requirements
============
@@ -11,13 +10,13 @@ Requirements
Instructions to download gradle wrapper
============
By running following command, it will download the gradle wrapper from Fineract git repository and put it under fineract-provider/gradle/wrapper
If the file fineract-provider/gradle/wrapper/gradle-wrapper.jar doesn't already exist in your copy of the Fineract codebase, the same needs to be downloaded using the commands below
wget --no-check-certificate -P fineract-provider/gradle/wrapper https://github.com/apache/incubator-fineract/raw/develop/fineract-provider/gradle/wrapper/gradle-wrapper.jar
wget --no-check-certificate -P fineract-provider/gradle/wrapper https://github.com/apache/fineract/raw/develop/fineract-provider/gradle/wrapper/gradle-wrapper.jar
(or)
curl --insecure -L https://github.com/apache/incubator-fineract/raw/develop/fineract-provider/gradle/wrapper/gradle-wrapper.jar > fineract-provider/gradle/wrapper/gradle-wrapper.jar
curl --insecure -L https://github.com/apache/fineract/raw/develop/fineract-provider/gradle/wrapper/gradle-wrapper.jar > fineract-provider/gradle/wrapper/gradle-wrapper.jar
Instructions to run Apache RAT (Release Audit Tool)
============
@@ -28,7 +27,7 @@ Instructions to run Apache RAT (Release Audit Tool)
Instructions to build a war file
============
1. Extract the archive file to your local directory.
2. Download gradle-wrapper.jar version 2.10 and place it in the fineract-provider/gradle/wrapper folder. See 'Instructions to download gradle wrapper' above.
2. Ensure gradle-wrapper.jar version 2.10 is present in the fineract-provider/gradle/wrapper folder. See 'Instructions to download gradle wrapper' above.
3. Run `./gradlew clean war` or `./gradlew build` to build deployable war file which will be created at build/libs directory.
@@ -36,8 +35,8 @@ Instructions to execute Integration tests
============
1. Login to mysql DB using `mysql -u root -p mysql`
> Note that if this is the first time to access MySQL DB, then you may need to reset your password.
2. Create the mifosplatform-tenants database using `CREATE DATABASE mifosplatform_tenants`.
3. Create the default tenant database using `CREATE DATABASE mifostenant_default`.
2. Create the mifosplatform-tenants database using `CREATE DATABASE mifosplatform-tenants`.
3. Create the default tenant database using `CREATE DATABASE mifostenant-default`.
4. Download gradle-wrapper.jar version 2.10 and place it in the fineract-provider/gradle/wrapper folder. See 'Instructions to download gradle wrapper' above.
5. Run the following commands:
1. `./gradlew migrateTenantListDB -PdbName=mifosplatform-tenants`
@@ -48,9 +47,9 @@ Instructions to execute Integration tests
Version
============
The latest stable release can be viewed on the develop branch: [Latest Release on Develop](https://github.com/apache/incubator-fineract/tree/develop "Latest Release").
The latest stable release can be viewed on the develop branch: [Latest Release on Develop](https://github.com/apache/fineract/tree/develop "Latest Release").
The progress of this project can be viewed here: [View change log](https://github.com/apache/incubator-fineract/blob/develop/CHANGELOG.md "Latest release change log")
The progress of this project can be viewed here: [View change log](https://github.com/apache/fineract/blob/develop/CHANGELOG.md "Latest release change log")
License
============
@@ -87,6 +86,15 @@ Video Demonstration
Apache Fineract / Mifos X Demo (November 2016) - <https://www.youtube.com/watch?v=h61g9TptMBo>
Governance and Policies
=======================
[Becoming a Committer](https://cwiki.apache.org/confluence/display/FINERACT/Becoming+a+Committer)
documents the process through which you can become a committer in this project.
[Pull Request Size Limit](https://cwiki.apache.org/confluence/display/FINERACT/Pull+Request+Size+Limit)
documents that we cannot accept huge "code dump" Pull Requests, with some related suggestions.
More Information
============
More details of the project can be found at <https://cwiki.apache.org/confluence/display/FINERACT>.
+122 -1
View File
@@ -3864,7 +3864,7 @@
<td>self/loans/{loanId}/guarantors</td>
<td></td>
<td><a href="#self_guarantors_list">Guarantors list</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
@@ -3875,6 +3875,7 @@
</td>
</td>
</tr>
<tr>
<td><a href="#self_service_user_reports">Run Report</a></td>
<td>self/runreports/{reportName}</td>
@@ -3904,6 +3905,30 @@
<td>self/surveys/scorecards/clients/{clientId}</td>
<td></td>
<td><a href="#survey_score">Survey Scorecards</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="#linkaccountstopocket">Link/Delink accounts to pocket</a></td>
<td>self/pockets?command=linkAccounts</td>
<td><a href="#linkaccountstopocket">Link accounts to pocket</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>self/pockets?command=delinkAccounts</td>
<td><a href="#delinkaccountstopocket">Delink accounts from pocket</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>self/pockets</td>
<td></td>
<td><a href="#retrievepocketaccounts">Retrieve accounts linked to pocket</a></td>
<td></td>
<td></td>
</tr>
@@ -50691,6 +50716,102 @@ GET https://DomainName/api/v1/self/runreports/{reportName}
</code>
</div>
</div>
<a id="linkaccountstopocket" name="linkaccountstopocket" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h2>Link/delink accounts to/from pocket</h2>
<p>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.</p>
<h2>Link accounts to pocket</h2>
<p>Example Requests:</p>
<div class=apiClick>self/pockets?command=linkAccounts</div>
</div>
<div class="method-example">
<code class="method-declaration">POST https://DomainName/api/v1/self/pockets?command=linkAccounts</code>
<code class="method-request">POST self/pockets?command=linkAccounts
Content-Type: application/json
Request Body:
{
"accountsDetail":[
{
"accountId":"11",
"accountType":"LOAN"
},
{
"accountId":"2",
"accountType":"SAVINGS"
}
]
}
</code>
<code class="method-response">
{
"resourceId": 6
}
</code>
</div>
</div>
<a id="delinkaccountstopocket" name="delinkaccountstopocket" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h2>Delink accounts from pocket</h2>
<p>Example Requests:</p>
<div class=apiClick>self/pockets?command=delinkAccounts</div>
</div>
<div class="method-example">
<code class="method-declaration">POST https://DomainName/api/v1/self/pockets?command=delinkAccounts</code>
<code class="method-request">POST self/pockets?command=delinkAccounts
Content-Type: application/json
Request Body:
{ "pocketAccountMappingIds" :[8,9]
}
</code>
<code class="method-response">
{
"resourceId":10
}
</code>
</div>
</div>
<a id="retrievepocketaccounts" name="retrievepocketaccounts" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h2>Retrieve accounts linked to pocket</h2>
<p>All linked loan </p>
<p>Example Requests:</p>
<div class=apiClick>self/pockets</div>
</div>
<div class="method-example">
<code class="method-declaration">POST https://DomainName/api/v1/self/pockets</code>
<code class="method-response">
{
"loanAccounts": [
{
"pocketId": 6,
"accountId": 11,
"accountType": 2,
"accountNumber": "000000011",
"id": 10
}
],
"savingsAccounts": [
{
"pocketId": 6,
"accountId": 2,
"accountType": 3,
"accountNumber": "000000002",
"id": 11
}
],
"shareAccounts": []
}
</code>
</div>
</div>
<!-- end of Customer Self Service APIs-->
</div>
<!-- main-content-wrapper -->
+8 -3
View File
@@ -57,8 +57,8 @@ sourceCompatibility = JavaVersion.VERSION_1_8
/* define binary compatibility version */
targetCompatibility = JavaVersion.VERSION_1_8
project.ext.springBootVersion = '1.1.6.RELEASE'
project.ext.springVersion = '4.0.7.RELEASE'
project.ext.springBootVersion = '1.2.8.RELEASE'
project.ext.springVersion = '4.1.9.RELEASE'
project.ext.springOauthVersion = '2.0.4.RELEASE'
project.ext.jerseyVersion = '1.17'
project.ext.springDataJpaVersion = '1.7.0.RELEASE' // also change spring-boot-gradle-plugin version above
@@ -92,6 +92,7 @@ rat {
excludes = [
'**/licenses/**',
'**/*.md',
'**/*.github/*',
'**/MANIFEST.MF',
'**/*.txt',
'**/*.log',
@@ -101,6 +102,8 @@ rat {
'**/.classpath',
'**/.project',
'**/.idea/**',
'**/*.ipr',
'**/*.iws',
'**/.settings/**',
'**/bin/**',
'**/.git/**',
@@ -155,7 +158,9 @@ rat {
'**/assets/html5shiv.js',
//BSD License
'**/assets/uglify.js'
'**/assets/uglify.js',
//Ignore out folder
'**/out/**'
]
}
+1 -1
View File
@@ -25,7 +25,7 @@ dependencies {
}
tomcat "org.apache.tomcat:tomcat-dbcp:${tomcatVersion}"
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat:${springBootVersion}")
providedCompile(
// [group: 'javax.servlet', name: 'servlet-api', version: '2.5'],
@@ -1634,9 +1634,10 @@ public class ClientSavingsIntegrationTest {
final String INTEREST_POSTING_DATE = dateFormat.format(interestPostingDate.getTime());
final String TODYS_POSTING_DATE = dateFormat.format(todysDate.getTime());
String withdrawBalance = "true";
if (TODYS_POSTING_DATE.equalsIgnoreCase(INTEREST_POSTING_DATE)) {
final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse);
final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, responseSpec);
validationErrorHelper.closeSavingsAccountPostInterestAndGetBackRequiredField(savingsId, withdrawBalance,
CommonConstants.RESPONSE_ERROR, CLOSEDON_DATE);
} else {
@@ -108,7 +108,7 @@ public class SchedulerJobsTest {
Thread.sleep(15000);
schedulerJob = this.schedulerJobHelper.getSchedulerJobById(this.requestSpec, this.responseSpec, jobId.toString());
Assert.assertNotNull(schedulerJob);
System.out.println("Job is Still Running");
System.out.println("Job " +jobId.toString() +" is Still Running");
}
ArrayList<HashMap> jobHistoryData = this.schedulerJobHelper.getSchedulerJobHistory(this.requestSpec, this.responseSpec,
jobId.toString());
@@ -24,6 +24,8 @@ import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
@@ -136,7 +138,6 @@ public class SchedulerJobsTestResults {
savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
String JobName = "Apply Annual Fee For Savings";
this.schedulerJobHelper.executeJob(JobName);
@@ -144,9 +145,14 @@ public class SchedulerJobsTestResults {
Float chargeAmount = (Float) chargeData.get("amount");
final HashMap summaryAfter = this.savingsAccountHelper.getSavingsSummary(savingsId);
Assert.assertEquals("Verifying Annual Fee after Running Scheduler Job for Apply Anual Fee", chargeAmount,
(Float) summaryAfter.get("totalAnnualFees"));
final HashMap savingsDetails = this.savingsAccountHelper.getSavingsDetails(savingsId);
final HashMap annualFeeDetails = (HashMap) savingsDetails.get("annualFee");
ArrayList<Integer> annualFeeDueDateAsArrayList = (ArrayList<Integer>) annualFeeDetails.get("dueDate");
LocalDate nextDueDateForAnnualFee = LocalDate.of(annualFeeDueDateAsArrayList.get(0), annualFeeDueDateAsArrayList.get(1), annualFeeDueDateAsArrayList.get(2));
LocalDate todaysDate = LocalDate.now(ZoneId.of("Asia/Kolkata"));
Assert.assertTrue("Verifying that all due Annual Fees have been paid ",
nextDueDateForAnnualFee.isAfter(todaysDate));
}
@@ -37,6 +37,7 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import javax.ws.rs.core.HttpHeaders;
@@ -59,6 +60,7 @@ public class ClientEntityImportHandlerTest {
}
@Test
@Ignore
public void testClientImport() throws InterruptedException, IOException, ParseException {
//in order to populate helper sheets
@@ -36,6 +36,7 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import javax.ws.rs.core.HttpHeaders;
@@ -58,6 +59,7 @@ public class LoanImportHandlerTest {
}
@Test
@Ignore
public void testLoanImport() throws InterruptedException, IOException, ParseException {
requestSpec.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
//in order to populate helper sheets
@@ -34,6 +34,7 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.io.*;
@@ -59,6 +60,7 @@ public class OfficeImportHandlerTest {
}
@Test
@Ignore
public void testOfficeImport() throws IOException, InterruptedException, NoSuchFieldException, ParseException {
OfficeHelper officeHelper=new OfficeHelper(requestSpec,responseSpec);
Workbook workbook=officeHelper.getOfficeWorkBook("dd MMMM yyyy");
@@ -41,6 +41,7 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import javax.ws.rs.core.HttpHeaders;
@@ -63,6 +64,7 @@ public class SavingsImportHandlerTest {
}
@Test
@Ignore
public void testSavingsImport() throws InterruptedException, IOException, ParseException {
requestSpec.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@@ -197,7 +197,7 @@ public class BatchHelper {
br.setReference(reference);
final String body = "{\"dateFormat\": \"dd MMMM yyyy\", \"locale\": \"en_GB\", \"clientId\": \"$.clientId\"," + "\"productId\": "
+ productId + ", \"principal\": \"10,000.00\", \"loanTermFrequency\": 12,"
+ productId + ", \"principal\": \"10,000.00\", \"loanTermFrequency\": 10,"
+ "\"loanTermFrequencyType\": 2, \"loanType\": \"individual\", \"numberOfRepayments\": 10,"
+ "\"repaymentEvery\": 1, \"repaymentFrequencyType\": 2, \"interestRatePerPeriod\": 10,"
+ "\"amortizationType\": 1, \"interestType\": 0, \"interestCalculationPeriodType\": 1,"
@@ -34,6 +34,7 @@ import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.google.gson.Gson;
@@ -210,7 +211,9 @@ public class ShareAccountIntegrationTests {
Assert.assertEquals("25", String.valueOf(summaryMap.get("totalApprovedShares")));
Assert.assertEquals("0", String.valueOf(summaryMap.get("totalPendingForApprovalShares")));
}
@Test
@Ignore
@SuppressWarnings("unchecked")
public void rejectShareAccount() {
shareProductHelper = new ShareProductHelper();
@@ -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;
}
}
@@ -23,6 +23,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.DefaultKeyGenerator;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
@@ -42,8 +44,20 @@ public class PlatformCacheConfiguration implements CachingConfigurer {
return this.delegatingCacheManager;
}
@Override
public CacheResolver cacheResolver() {
//TODO https://issues.apache.org/jira/browse/FINERACT-705
return null;
}
@Override
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
@Override
public CacheErrorHandler errorHandler() {
//TODO https://issues.apache.org/jira/browse/FINERACT-705
return null;
}
}
@@ -25,13 +25,17 @@ public class SMTPCredentialsData {
private final String host;
private final String port;
private final boolean useTLS;
private final String fromEmail;
private final String fromName;
public SMTPCredentialsData(final String username, final String password, final String host, final String port, final boolean useTLS) {
public SMTPCredentialsData(final String username, final String password, final String host, final String port, final boolean useTLS, String fromEmail, String fromName) {
this.username = username;
this.password = password;
this.host = host;
this.port = port;
this.useTLS = useTLS;
this.fromEmail = fromEmail;
this.fromName = fromName;
}
public String getUsername() {
@@ -54,4 +58,11 @@ public class SMTPCredentialsData {
return useTLS;
}
public String getFromEmail() {
return fromEmail != null ?fromEmail :username;
}
public String getFromName() {
return fromName != null ?fromName :username;
}
}
@@ -34,6 +34,8 @@ public class ExternalServicesConstants {
public static final String SMTP_HOST = "host";
public static final String SMTP_PORT = "port";
public static final String SMTP_USE_TLS = "useTLS";
public static final String SMTP_FROM_EMAIL = "fromEmail";
public static final String SMTP_FROM_NAME = "fromName";
public static final String SMS_SERVICE_NAME = "MESSAGE_GATEWAY";
public static final String SMS_HOST = "host_name";
@@ -78,7 +80,7 @@ public class ExternalServicesConstants {
}
public static enum SMTP_JSON_INPUT_PARAMS {
USERNAME("username"), PASSWORD("password"), HOST("host"), PORT("port"), USETLS("useTLS");
USERNAME("username"), PASSWORD("password"), HOST("host"), PORT("port"), USETLS("useTLS"), FROM_EMAIL("fromEmail"), FROM_NAME("fromName");
private final String value;
@@ -75,21 +75,30 @@ public class ExternalServicesPropertiesReadPlatformServiceImpl implements Extern
String host = null;
String port = "25";
boolean useTLS = false;
String fromEmail = null;
String fromName = null;
while (rs.next()) {
if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_USERNAME)) {
username = rs.getString("value");
} else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_PASSWORD)) {
password = rs.getString("value");
} else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_HOST)) {
host = rs.getString("value");
} else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_PORT)) {
port = rs.getString("value");
} else if (rs.getString("name").equalsIgnoreCase(ExternalServicesConstants.SMTP_USE_TLS)) {
useTLS = Boolean.parseBoolean(rs.getString("value"));
String name = rs.getString("name");
String value = rs.getString("value");
if (ExternalServicesConstants.SMTP_USERNAME.equalsIgnoreCase(name)) {
username = value;
} else if (ExternalServicesConstants.SMTP_PASSWORD.equalsIgnoreCase(name)) {
password = value;
} else if (ExternalServicesConstants.SMTP_HOST.equalsIgnoreCase(name)) {
host = value;
} else if (ExternalServicesConstants.SMTP_PORT.equalsIgnoreCase(name)) {
port = value;
} else if (ExternalServicesConstants.SMTP_USE_TLS.equalsIgnoreCase(name)) {
useTLS = Boolean.parseBoolean(value);
} else if (ExternalServicesConstants.SMTP_FROM_EMAIL.equalsIgnoreCase(name)) {
fromEmail = value;
} else if (ExternalServicesConstants.SMTP_FROM_NAME.equalsIgnoreCase(name)) {
fromName = value;
}
}
return new SMTPCredentialsData(username, password, host, port, useTLS);
return new SMTPCredentialsData(username, password, host, port, useTLS, fromEmail, fromName);
}
}
@@ -60,7 +60,6 @@ public class GmailBackedPlatformEmailService implements PlatformEmailService {
public void sendDefinedEmail(EmailDetail emailDetails) {
final Email email = new SimpleEmail();
final SMTPCredentialsData smtpCredentialsData = this.externalServicesReadPlatformService.getSMTPCredentials();
final String authuserName = smtpCredentialsData.getUsername();
final String authuser = smtpCredentialsData.getUsername();
final String authpwd = smtpCredentialsData.getPassword();
@@ -74,7 +73,7 @@ public class GmailBackedPlatformEmailService implements PlatformEmailService {
if(smtpCredentialsData.isUseTLS()){
email.getMailSession().getProperties().put("mail.smtp.starttls.enable", "true");
}
email.setFrom(authuser, authuserName);
email.setFrom(smtpCredentialsData.getFromEmail(), smtpCredentialsData.getFromName());
email.setSubject(emailDetails.getSubject());
email.setMsg(emailDetails.getBody());
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.infrastructure.jobs.service;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -382,7 +383,7 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
return targetObject;
}
private Trigger createTrigger(final ScheduledJobDetail scheduledJobDetails, final JobDetail jobDetail) {
private Trigger createTrigger(final ScheduledJobDetail scheduledJobDetails, final JobDetail jobDetail) throws ParseException {
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
final CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setName(scheduledJobDetails.getJobName() + "Trigger" + tenant.getId());
@@ -713,7 +713,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
throw new InvalidClientStateTransitionException("close", "is.under.transfer", errorMessage);
}
if (client.isNotPending() && client.getActivationLocalDate().isAfter(closureDate)) {
if (client.isNotPending() && client.getActivationLocalDate() != null && client.getActivationLocalDate().isAfter(closureDate)) {
final String errorMessage = "The client closureDate cannot be before the client ActivationDate.";
throw new InvalidClientStateTransitionException("close", "date.cannot.before.client.actvation.date", errorMessage,
closureDate, client.getActivationLocalDate());
@@ -134,7 +134,7 @@ public class LoanCharge extends AbstractPersistableCustom<Long> {
public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
if (dueDate == null) {
if (chargeDefinition.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) && dueDate == null) {
final String defaultUserMessage = "Loan charge is missing due date.";
throw new LoanChargeWithoutMandatoryFieldException("loanCharge", "dueDate", defaultUserMessage, chargeDefinition.getId(),
chargeDefinition.getName());
@@ -134,4 +134,7 @@ public interface LoanReadPlatformService {
LoanAccountData retrieveLoanByLoanAccount(String loanAccountNumber);
Long retrieveLoanIdByAccountNumber(String loanAccountNumber);
String retrieveAccountNumberByAccountId(Long accountId);
}
@@ -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);
}
}
}
@@ -23,6 +23,8 @@ import java.util.List;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -79,8 +81,15 @@ public class SavingsAccountRepositoryWrapper {
loadLazyCollections(accounts);
return accounts ;
}
@Transactional(readOnly=true)
public Page<SavingsAccount> findByStatus(Integer status, Pageable pageable) {
Page<SavingsAccount> accounts = this.repository.findByStatus(status, pageable);
loadLazyCollections(accounts);
return accounts;
}
//Root Entities are enough
//Root Entities are enough
public List<SavingsAccount> findByClientIdAndGroupId(@Param("clientId") Long clientId, @Param("groupId") Long groupId) {
return this.repository.findByClientIdAndGroupId(clientId, groupId) ;
}
@@ -118,4 +127,10 @@ public class SavingsAccountRepositoryWrapper {
}
}
}
private void loadLazyCollections(Page<SavingsAccount> accounts) {
for (SavingsAccount account : accounts) {
account.loadLazyCollections();
}
}
}
@@ -60,4 +60,6 @@ public interface SavingsAccountReadPlatformService {
List<Long> retrieveSavingsIdsPendingEscheat(LocalDate tenantLocalDate);
boolean isAccountBelongsToClient(final Long clientId, final Long accountId, final DepositAccountType depositAccountType, final String currencyCode) ;
String retrieveAccountNumberByAccountId(Long accountId);
}
@@ -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);
}
}
}
@@ -364,9 +364,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
.findOneWithNotFoundDetection(savingsAccountChargeId, accountId);
final LocalDate todaysDate = DateUtils.getLocalDateOfTenant();
final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy");
this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(), fmt, user);
fmt.withZone(DateUtils.getDateTimeZoneOfTenant());
while (todaysDate.isAfter(savingsAccountCharge.getDueLocalDate())) {
this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(),
fmt, user);
}
return new CommandProcessingResultBuilder() //
.withEntityId(savingsAccountCharge.getId()) //
.withOfficeId(savingsAccountCharge.savingsAccount().officeId()) //
@@ -27,6 +27,7 @@ import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
@@ -40,12 +41,12 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService {
private final SavingsAccountAssembler savingAccountAssembler;
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
private final SavingsAccountReadPlatformService savingAccountReadPlatformService;
private final SavingsAccountRepository savingsAccountRepository;
private final SavingsAccountRepositoryWrapper savingsAccountRepository;
@Autowired
public SavingsSchedularServiceImpl(final SavingsAccountAssembler savingAccountAssembler,
final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
final SavingsAccountReadPlatformService savingAccountReadPlatformService, final SavingsAccountRepository savingsAccountRepository) {
final SavingsAccountReadPlatformService savingAccountReadPlatformService, final SavingsAccountRepositoryWrapper savingsAccountRepository) {
this.savingAccountAssembler = savingAccountAssembler;
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
this.savingAccountReadPlatformService = savingAccountReadPlatformService;
@@ -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.";
}
@@ -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);
}
}
@@ -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<PocketAccountMapping> loanAccounts;
private final Collection<PocketAccountMapping> savingsAccounts;
private final Collection<PocketAccountMapping> shareAccounts;
private PocketAccountMappingData(final Collection<PocketAccountMapping> loanAccounts, Collection<PocketAccountMapping> savingsAccounts, final Collection<PocketAccountMapping> shareAccounts ) {
this.loanAccounts = loanAccounts;
this.savingsAccounts = savingsAccounts;
this.shareAccounts = shareAccounts;
}
public static PocketAccountMappingData instance(final Collection<PocketAccountMapping> loanAccounts, final Collection<PocketAccountMapping> savingsAccounts, final Collection<PocketAccountMapping> shareAccounts) {
return new PocketAccountMappingData(loanAccounts, savingsAccounts, shareAccounts);
}
}
@@ -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<String> linkingAccountsSupportedParameters = new HashSet<>(
Arrays.asList(PocketApiConstants.accountIdParamName, PocketApiConstants.accountTypeParamName,
PocketApiConstants.accountsDetail));
private final Set<String> 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<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.linkingAccountsSupportedParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(PocketApiConstants.pocketsResourceName);
final JsonElement element = this.fromApiJsonHelper.parse(json);
JsonArray accountsDetail = this.fromApiJsonHelper.extractJsonArrayNamed(PocketApiConstants.accountsDetail,
element);
baseDataValidator.reset().parameter(PocketApiConstants.accountsDetail).value(accountsDetail).notNull()
.jsonArrayNotEmpty();
final List<String> valueList = Arrays.asList(EntityAccountType.LOAN.name().toLowerCase(),
EntityAccountType.SAVINGS.name().toLowerCase(), EntityAccountType.SHARES.name().toLowerCase());
for (JsonElement accountDetails : accountsDetail) {
final Long accountId = this.fromApiJsonHelper.extractLongNamed(PocketApiConstants.accountIdParamName,
accountDetails);
baseDataValidator.reset().parameter(PocketApiConstants.accountIdParamName).value(accountId).notBlank();
final String accountType = this.fromApiJsonHelper
.extractStringNamed(PocketApiConstants.accountTypeParamName, accountDetails);
baseDataValidator.reset().parameter(PocketApiConstants.accountTypeParamName).value(accountType).notBlank()
.isOneOfTheseStringValues(valueList);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForDeLinkingAccounts(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
this.delinkingAccountsSupportedParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(PocketApiConstants.pocketsResourceName);
final JsonElement element = this.fromApiJsonHelper.parse(json);
JsonArray pocketAccountMappingList = this.fromApiJsonHelper
.extractJsonArrayNamed(PocketApiConstants.pocketAccountMappingList, element);
baseDataValidator.reset().parameter(PocketApiConstants.pocketAccountMappingList).value(pocketAccountMappingList)
.notNull().jsonArrayNotEmpty();
for (JsonElement pocketAccountMapping : pocketAccountMappingList) {
final Long mappingId = pocketAccountMapping.getAsLong();
baseDataValidator.reset().parameter(PocketApiConstants.pocketAccountMappingId).value(mappingId).notBlank();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(PocketApiConstants.dataValidationMessage,
PocketApiConstants.validationErrorMessage, dataValidationErrors);
}
}
}
@@ -0,0 +1,59 @@
/**
* 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.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@SuppressWarnings("serial")
@Entity
@Table(name = "m_pocket", uniqueConstraints = {
@UniqueConstraint(columnNames = { "app_user_id" }, name = "unique_app_user") })
public class Pocket extends AbstractPersistableCustom<Long> {
@Column(name = "app_user_id", length = 20, nullable = false)
private Long appUserId;
public Long getAppUserId() {
return appUserId;
}
public void setAppUserId(Long appUserId) {
this.appUserId = appUserId;
}
protected Pocket() {
}
private Pocket(final Long appUserId) {
this.appUserId = appUserId;
}
public static Pocket instance(final Long appUserId) {
return new Pocket(appUserId);
}
}
@@ -0,0 +1,86 @@
/**
* 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.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@SuppressWarnings("serial")
@Entity
@Table(name = "m_pocket_accounts_mapping")
public class PocketAccountMapping extends AbstractPersistableCustom<Long> {
@Column(name = "pocket_id", length = 20, nullable = false)
private Long pocketId;
@Column(name = "account_id", length = 20, nullable = false)
private Long accountId;
@Column(name = "account_type", nullable = false)
private Integer accountType;
@Column(name = "account_number", nullable = false)
private String accountNumber;
protected PocketAccountMapping() {
}
private PocketAccountMapping(final Long pocketId, final Long accountId, final Integer accountType,
final String accountNumber) {
this.pocketId = pocketId;
this.accountId = accountId;
this.accountType = accountType;
this.accountNumber = accountNumber;
}
public static PocketAccountMapping instance(final Long pocketId, final Long accountId, final Integer accountType,
final String accountNumber) {
return new PocketAccountMapping(pocketId, accountId, accountType, accountNumber);
}
public Long getPocketId() {
return pocketId;
}
public void setPocketId(Long pocketId) {
this.pocketId = pocketId;
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public Integer getAccountType() {
return accountType;
}
public void setAccountType(Integer accountType) {
this.accountType = accountType;
}
}
@@ -0,0 +1,38 @@
/**
* 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.domain;
import java.util.Collection;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface PocketAccountMappingRepository
extends JpaRepository<PocketAccountMapping, Long>, JpaSpecificationExecutor<PocketAccountMapping> {
@Query("select pam from PocketAccountMapping pam where pam.id = :id and pam.pocketId =:pocketId")
PocketAccountMapping findByIdAndPocketId(@Param("id") Long id, @Param("pocketId") Long pocketId);
@Query("select pam from PocketAccountMapping pam where pam.pocketId =:pocketId")
Collection<PocketAccountMapping> findByPocketId(@Param("pocketId") Long pocketId);
}
@@ -0,0 +1,66 @@
/**
* 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.domain;
import java.util.Collection;
import java.util.List;
import org.apache.fineract.portfolio.self.pockets.exception.MappingIdNotLinkedToPocketException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PocketAccountMappingRepositoryWrapper {
private final PocketAccountMappingRepository pocketAccountMappingRepository;
@Autowired
public PocketAccountMappingRepositoryWrapper(final PocketAccountMappingRepository pocketAccountMappingRepository) {
this.pocketAccountMappingRepository = pocketAccountMappingRepository;
}
public void save(final PocketAccountMapping pocketAccountMapping) {
this.pocketAccountMappingRepository.save(pocketAccountMapping);
}
public List<PocketAccountMapping> save(final List<PocketAccountMapping> pocketAccountsList) {
return this.pocketAccountMappingRepository.save(pocketAccountsList);
}
public void delete(final List<PocketAccountMapping> pocketAccountsList) {
this.pocketAccountMappingRepository.delete(pocketAccountsList);
}
public PocketAccountMapping findByIdAndPocketIdWithNotFoundException(final Long id, final Long pocketId) {
PocketAccountMapping pocketAccountMapping = this.pocketAccountMappingRepository.findByIdAndPocketId(id,
pocketId);
if (pocketAccountMapping == null) {
throw new MappingIdNotLinkedToPocketException(id);
}
return pocketAccountMapping;
}
public Collection<PocketAccountMapping> findByPocketId(final Long pocketId) {
return this.pocketAccountMappingRepository.findByPocketId(pocketId);
}
}
@@ -0,0 +1,32 @@
/**
* 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.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface PocketRepository extends JpaRepository<Pocket, Long> , JpaSpecificationExecutor<Pocket>{
@Query("select pocket.id from Pocket pocket where pocket.appUserId= :appUserId")
Long findByAppUserId(@Param("appUserId") Long appUserId);
}
@@ -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.pockets.domain;
import org.apache.fineract.portfolio.self.pockets.exception.PocketNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PocketRepositoryWrapper {
private final PocketRepository pocketRepository;
@Autowired
public PocketRepositoryWrapper(final PocketRepository pocketRepository) {
this.pocketRepository = pocketRepository;
}
public void saveAndFlush(final Pocket pocket) {
this.pocketRepository.saveAndFlush(pocket);
}
public Long findByAppUserId(final Long appUserId) {
return this.pocketRepository.findByAppUserId(appUserId);
}
public Long findByAppUserIdWithNotFoundDetection(final Long appUserId) {
final Long pocketId = this.pocketRepository.findByAppUserId(appUserId);
if (pocketId == null) {
throw new PocketNotFoundException();
}
return pocketId;
}
}
@@ -0,0 +1,31 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
import org.apache.fineract.portfolio.self.pockets.api.PocketApiConstants;
@SuppressWarnings("serial")
public class MappingIdNotLinkedToPocketException extends AbstractPlatformDomainRuleException {
public MappingIdNotLinkedToPocketException(final Long id) {
super(PocketApiConstants.mappingIdNotLinkedToPocketException,
PocketApiConstants.mappingIdNotLinkedToPocketErrorMessage, id);
}
}
@@ -0,0 +1,32 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
import org.apache.fineract.portfolio.self.pockets.api.PocketApiConstants;
@SuppressWarnings("serial")
public class PocketNotFoundException extends AbstractPlatformDomainRuleException {
public PocketNotFoundException() {
super(PocketApiConstants.pocketNotFoundException, PocketApiConstants.pocketNotFoundErrorMessage);
}
}
@@ -0,0 +1,48 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.self.pockets.handler;
import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.self.pockets.api.PocketApiConstants;
import org.apache.fineract.portfolio.self.pockets.service.PocketWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@CommandType(entity = PocketApiConstants.pocketEntityName, action = PocketApiConstants.delinkAccountsActionName)
public class DelinkAccountsFromPocketCommandHandler implements NewCommandSourceHandler {
private final PocketWritePlatformService pocketWritePlatformService;
@Autowired
public DelinkAccountsFromPocketCommandHandler(final PocketWritePlatformService pocketWritePlatformService) {
this.pocketWritePlatformService = pocketWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.pocketWritePlatformService.delinkAccounts(command);
}
}
@@ -0,0 +1,50 @@
/**
* 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.handler;
import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.self.pockets.api.PocketApiConstants;
import org.apache.fineract.portfolio.self.pockets.service.PocketWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@CommandType(entity = PocketApiConstants.pocketEntityName, action = PocketApiConstants.linkAccountsActionName)
public class LinkAccountsToPocketCommandHandler implements NewCommandSourceHandler {
private final PocketWritePlatformService pocketWritePlatformService;
@Autowired
public LinkAccountsToPocketCommandHandler(final PocketWritePlatformService pocketWritePlatformService) {
this.pocketWritePlatformService = pocketWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.pocketWritePlatformService.linkAccounts(command);
}
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.self.pockets.service;
public interface AccountEntityService {
String getKey();
void validateSelfUserAccountMapping(Long accountId);
String retrieveAccountNumberByAccountId(Long accountId);
}
@@ -0,0 +1,48 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.self.pockets.service;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("singleton")
public class AccountEntityServiceFactory {
private Map<String, AccountEntityService> accountEntityServiceHashMap = new HashMap<>();
@Autowired
public AccountEntityServiceFactory(final Set<AccountEntityService> accountEntityServices) {
for (AccountEntityService service : accountEntityServices) {
this.accountEntityServiceHashMap.put(service.getKey(), service);
}
}
public AccountEntityService getAccountEntityService(final String key) {
return this.accountEntityServiceHashMap.get(key);
}
}
@@ -0,0 +1,67 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.self.pockets.service;
import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.self.loanaccount.service.AppuserLoansMapperReadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountEntityServiceForLoanImpl implements AccountEntityService {
private final String KEY = EntityAccountType.LOAN.name();
private final PlatformSecurityContext context;
private final AppuserLoansMapperReadService appuserLoansMapperReadService;
private final LoanReadPlatformService loanReadPlatformService;
@Autowired
public AccountEntityServiceForLoanImpl(final PlatformSecurityContext context,
final AppuserLoansMapperReadService appuserLoansMapperReadService,
final LoanReadPlatformService loanReadPlatformService) {
this.context = context;
this.appuserLoansMapperReadService = appuserLoansMapperReadService;
this.loanReadPlatformService = loanReadPlatformService;
}
@Override
public String getKey() {
return KEY;
}
@Override
public void validateSelfUserAccountMapping(Long accountId) {
if (!this.appuserLoansMapperReadService.isLoanMappedToUser(accountId,
this.context.authenticatedUser().getId())) {
throw new LoanNotFoundException(accountId);
}
}
@Override
public String retrieveAccountNumberByAccountId(Long accountId) {
return this.loanReadPlatformService.retrieveAccountNumberByAccountId(accountId);
}
}
@@ -0,0 +1,70 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.self.pockets.service;
import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
import org.apache.fineract.portfolio.self.savings.service.AppuserSavingsMapperReadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountEntityServiceForSavingsImpl implements AccountEntityService {
private final String KEY = EntityAccountType.SAVINGS.name();
private final PlatformSecurityContext context;
private final AppuserSavingsMapperReadService appuserSavingsMapperReadService;
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
@Autowired
public AccountEntityServiceForSavingsImpl(final PlatformSecurityContext context,
final AppuserSavingsMapperReadService appuserSavingsMapperReadService,
final SavingsAccountReadPlatformService savingsAccountReadPlatformService) {
this.context = context;
this.appuserSavingsMapperReadService = appuserSavingsMapperReadService;
this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
}
@Override
public String getKey() {
return KEY;
}
@Override
public void validateSelfUserAccountMapping(Long accountId) {
if (!this.appuserSavingsMapperReadService.isSavingsMappedToUser(accountId,
this.context.getAuthenticatedUserIfPresent().getId())) {
throw new SavingsAccountNotFoundException(accountId);
}
}
@Override
public String retrieveAccountNumberByAccountId(Long accountId) {
return this.savingsAccountReadPlatformService.retrieveAccountNumberByAccountId(accountId);
}
}
@@ -0,0 +1,69 @@
/**
* 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.service;
import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.accounts.exceptions.ShareAccountNotFoundException;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
import org.apache.fineract.portfolio.self.shareaccounts.service.AppUserShareAccountsMapperReadPlatformService;
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountEntityServiceForShareAccountsImpl implements AccountEntityService {
private final String KEY = EntityAccountType.SHARES.name();
private final PlatformSecurityContext context;
private final AppUserShareAccountsMapperReadPlatformService appUserShareAccountsMapperReadPlatformService;
private final ShareAccountReadPlatformService shareAccountReadPlatformService;
@Autowired
public AccountEntityServiceForShareAccountsImpl(final PlatformSecurityContext context,
final AppUserShareAccountsMapperReadPlatformService appUserShareAccountsMapperReadPlatformService,
final ShareAccountReadPlatformService shareAccountReadPlatformService) {
this.context = context;
this.appUserShareAccountsMapperReadPlatformService = appUserShareAccountsMapperReadPlatformService;
this.shareAccountReadPlatformService = shareAccountReadPlatformService;
}
@Override
public String getKey() {
return KEY;
}
@Override
public void validateSelfUserAccountMapping(Long accountId) {
if (!this.appUserShareAccountsMapperReadPlatformService.isShareAccountsMappedToUser(accountId,
this.context.getAuthenticatedUserIfPresent().getId())) {
throw new ShareAccountNotFoundException(accountId);
}
}
@Override
public String retrieveAccountNumberByAccountId(Long accountId) {
return this.shareAccountReadPlatformService.retrieveAccountNumberByAccountId(accountId);
}
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.self.pockets.service;
import org.apache.fineract.portfolio.self.pockets.data.PocketAccountMappingData;
public interface PocketAccountMappingReadPlatformService {
PocketAccountMappingData retrieveAll();
boolean validatePocketAndAccountMapping(Long pocketId, Long accountId, Integer accountType);
}
@@ -0,0 +1,91 @@
/**
* 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.service;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.self.pockets.data.PocketAccountMappingData;
import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMapping;
import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMappingRepositoryWrapper;
import org.apache.fineract.portfolio.self.pockets.domain.PocketRepositoryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class PocketAccountMappingReadPlatformServiceImpl implements PocketAccountMappingReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
private final PocketRepositoryWrapper pocketRepositoryWrapper;
private final PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper;
@Autowired
public PocketAccountMappingReadPlatformServiceImpl(final RoutingDataSource dataSource,
final PlatformSecurityContext context, final PocketRepositoryWrapper pocketRepositoryWrapper,
final PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.context = context;
this.pocketRepositoryWrapper = pocketRepositoryWrapper;
this.pocketAccountMappingRepositoryWrapper = pocketAccountMappingRepositoryWrapper;
}
@Override
public PocketAccountMappingData retrieveAll() {
final Long pocketId = this.pocketRepositoryWrapper.findByAppUserId(this.context.authenticatedUser().getId());
if (pocketId != null) {
Collection<PocketAccountMapping> pocketAccountMappingList = this.pocketAccountMappingRepositoryWrapper
.findByPocketId(pocketId);
if (pocketAccountMappingList != null && !pocketAccountMappingList.isEmpty()) {
Collection<PocketAccountMapping> loanAccounts = new ArrayList<>();
Collection<PocketAccountMapping> savingsAccounts = new ArrayList<>();
Collection<PocketAccountMapping> shareAccounts = new ArrayList<>();
for (PocketAccountMapping pocketMapping : pocketAccountMappingList) {
if (pocketMapping.getAccountType().equals(EntityAccountType.LOAN.getValue())) {
loanAccounts.add(pocketMapping);
} else if (pocketMapping.getAccountType().equals(EntityAccountType.SAVINGS.getValue())) {
savingsAccounts.add(pocketMapping);
} else {
shareAccounts.add(pocketMapping);
}
}
return PocketAccountMappingData.instance(loanAccounts, savingsAccounts, shareAccounts);
}
}
return null;
}
@Override
public boolean validatePocketAndAccountMapping(Long pocketId, Long accountId, Integer accountType) {
final String sql = "select count(id) from m_pocket_accounts_mapping mapping where pocket_id = ? and account_id = ? and account_type = ?";
try {
return this.jdbcTemplate.queryForObject(sql, new Object[] { pocketId, accountId, accountType },
Boolean.class);
} catch (EmptyResultDataAccessException e) {
return false;
}
}
}
@@ -0,0 +1,32 @@
/**
* 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.service;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface PocketWritePlatformService {
CommandProcessingResult linkAccounts(JsonCommand command);
CommandProcessingResult delinkAccounts(JsonCommand command);
}
@@ -0,0 +1,138 @@
/**
* 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.service;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.self.pockets.api.PocketApiConstants;
import org.apache.fineract.portfolio.self.pockets.data.PocketDataValidator;
import org.apache.fineract.portfolio.self.pockets.domain.Pocket;
import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMapping;
import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMappingRepositoryWrapper;
import org.apache.fineract.portfolio.self.pockets.domain.PocketRepositoryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@Service
public class PocketWritePlatformServiceImpl implements PocketWritePlatformService {
private final PlatformSecurityContext context;
private final PocketDataValidator pocketDataValidator;
private final AccountEntityServiceFactory accountEntityServiceFactory;
private final PocketRepositoryWrapper pocketRepositoryWrapper;
private final PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper;
private final PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService;
@Autowired
public PocketWritePlatformServiceImpl(final PlatformSecurityContext context,
PocketDataValidator pocketDataValidator, final AccountEntityServiceFactory accountEntityServiceFactory,
final PocketRepositoryWrapper pocketRepositoryWrapper,
final PocketAccountMappingRepositoryWrapper pocketAccountMappingRepositoryWrapper,
final PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService) {
this.context = context;
this.pocketDataValidator = pocketDataValidator;
this.accountEntityServiceFactory = accountEntityServiceFactory;
this.pocketRepositoryWrapper = pocketRepositoryWrapper;
this.pocketAccountMappingRepositoryWrapper = pocketAccountMappingRepositoryWrapper;
this.pocketAccountMappingReadPlatformService = pocketAccountMappingReadPlatformService;
}
@Transactional
@Override
public CommandProcessingResult linkAccounts(JsonCommand command) {
this.pocketDataValidator.validateForLinkingAccounts(command.json());
JsonArray accountsDetail = command.arrayOfParameterNamed(PocketApiConstants.accountsDetail);
Long pocketId = this.pocketRepositoryWrapper.findByAppUserId(this.context.authenticatedUser().getId());
if (pocketId == null) {
final Pocket pocket = Pocket.instance(this.context.authenticatedUser().getId());
this.pocketRepositoryWrapper.saveAndFlush(pocket);
pocketId = pocket.getId();
}
final List<PocketAccountMapping> pocketAccounts = new ArrayList<>();
for (int i = 0; i < accountsDetail.size(); i++) {
final JsonObject element = accountsDetail.get(i).getAsJsonObject();
final Long accountId = element.get(PocketApiConstants.accountIdParamName).getAsLong();
final String accountType = element.get(PocketApiConstants.accountTypeParamName).getAsString();
final AccountEntityService accountEntityService = this.accountEntityServiceFactory
.getAccountEntityService(accountType);
accountEntityService.validateSelfUserAccountMapping(accountId);
Integer accountTypeValue = EntityAccountType.valueOf(accountType).getValue();
if (this.pocketAccountMappingReadPlatformService.validatePocketAndAccountMapping(pocketId, accountId,
accountTypeValue)) {
throw new PlatformDataIntegrityException(PocketApiConstants.duplicateMappingException,
PocketApiConstants.duplicateMappingExceptionMessage, accountId, accountType);
}
final String accountNumber = accountEntityService.retrieveAccountNumberByAccountId(accountId);
pocketAccounts.add(PocketAccountMapping.instance(pocketId, accountId, accountTypeValue, accountNumber));
}
this.pocketAccountMappingRepositoryWrapper.save(pocketAccounts);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(pocketId).build();
}
@Override
public CommandProcessingResult delinkAccounts(JsonCommand command) {
this.pocketDataValidator.validateForDeLinkingAccounts(command.json());
JsonArray pocketAccountMappingList = command.arrayOfParameterNamed(PocketApiConstants.pocketAccountMappingList);
Long pocketId = this.pocketRepositoryWrapper
.findByAppUserIdWithNotFoundDetection(this.context.authenticatedUser().getId());
final List<PocketAccountMapping> pocketAccounts = new ArrayList<>();
for (JsonElement mapping : pocketAccountMappingList) {
final Long mappingId = mapping.getAsLong();
PocketAccountMapping pocketAccountMapping = this.pocketAccountMappingRepositoryWrapper
.findByIdAndPocketIdWithNotFoundException(mappingId, pocketId);
if (pocketAccountMapping != null) {
pocketAccounts.add(pocketAccountMapping);
}
}
this.pocketAccountMappingRepositoryWrapper.delete(pocketAccounts);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(pocketId).build();
}
}
@@ -37,5 +37,6 @@ public interface ShareAccountReadPlatformService extends AccountReadPlatformServ
public Set<String> getResponseDataParams();
Collection<ShareAccountData> retrieveAllShareAccountDataForDividends(Long productId, boolean fetchInActiveAccounts, LocalDate startDate);
String retrieveAccountNumberByAccountId(Long accountId);
}
@@ -38,6 +38,7 @@ import org.apache.fineract.portfolio.accountdetails.data.ShareAccountSummaryData
import org.apache.fineract.portfolio.accounts.constants.AccountsApiConstants;
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.data.ClientData;
@@ -64,6 +65,7 @@ import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@@ -502,4 +504,14 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat
return this.schema;
}
}
@Override
public String retrieveAccountNumberByAccountId(Long accountId) {
try {
final String sql = "select s.account_no from m_share_account s where s.id = ?";
return this.jdbcTemplate.queryForObject(sql, new Object[] { accountId }, String.class);
} catch (final EmptyResultDataAccessException e) {
throw new ShareAccountNotFoundException(accountId);
}
}
}
@@ -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.
--
INSERT INTO `c_external_service_properties` (`name`, `value`, `external_service_id`)
(SELECT
'fromEmail' as name,
value as value,
c_external_service.id as external_service_id
FROM c_external_service_properties
JOIN c_external_service ON c_external_service_properties.external_service_id=c_external_service.id
WHERE c_external_service_properties.name='username'
AND c_external_service.name='SMTP_Email_Account')
UNION ALL
(SELECT
'fromName' as name,
value as value,
c_external_service.id as external_service_id
FROM c_external_service_properties
JOIN c_external_service ON c_external_service_properties.external_service_id=c_external_service.id
WHERE c_external_service_properties.name='username'
AND c_external_service.name='SMTP_Email_Account');
@@ -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.
--
CREATE TABLE `m_pocket` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`app_user_id` BIGINT(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `app_user_id` (`app_user_id`),
CONSTRAINT `FK__m_appuser__pocket` FOREIGN KEY (`app_user_id`) REFERENCES `m_appuser` (`id`)
);
CREATE TABLE `m_pocket_accounts_mapping` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`pocket_id` BIGINT(20) NOT NULL,
`account_id` BIGINT(20) NOT NULL,
`account_type` INT(5) NOT NULL,
`account_number` VARCHAR(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `unique_pocket_mapping` (`pocket_id`, `account_id`, `account_type`),
CONSTRAINT `FK__m_pocket` FOREIGN KEY (`pocket_id`) REFERENCES `m_pocket` (`id`)
);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'LINK_ACCOUNT_TO_POCKET', 'POCKET', 'LINK_ACCOUNT_TO', 0),
('portfolio', 'DELINK_ACCOUNT_FROM_POCKET', 'POCKET', 'DELINK_ACCOUNT_FROM', 0);