diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD new file mode 100644 index 00000000000..586f5f7b39b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.MD @@ -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 diff --git a/.gitignore b/.gitignore index c7f7defc6a4..4232f6312fa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,9 @@ *.ear .gradle build +*.iml +*.ipr +*.iws +*.DS_Store +.idea +gradle \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 571803518cb..39265046c82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index 58e9879f723..22d391d7ed4 100644 --- a/README.md +++ b/README.md @@ -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) - +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 . diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index a02cae75c26..1a98be002d2 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -3864,7 +3864,7 @@

Self Service

self/loans/{loanId}/guarantors Guarantors list - + @@ -3875,6 +3875,7 @@

Self Service

+ Run Report self/runreports/{reportName} @@ -3904,6 +3905,30 @@

Self Service

self/surveys/scorecards/clients/{clientId} Survey Scorecards + + + + + Link/Delink accounts to pocket + self/pockets?command=linkAccounts + Link accounts to pocket + + + + + + + self/pockets?command=delinkAccounts + Delink accounts from pocket + + + + + + + self/pockets + + Retrieve accounts linked to pocket @@ -50691,6 +50716,102 @@
Arguments
+ +   +
+
+

Link/delink accounts to/from pocket

+

Pockets behave as favourites. An user can link his/her Loan, Savings and Share accounts to pocket for faster access. In a similar way linked accounts can be delinked from the pocket.

+

Link accounts to pocket

+

Example Requests:

+
self/pockets?command=linkAccounts
+
+
+ POST https://DomainName/api/v1/self/pockets?command=linkAccounts + POST self/pockets?command=linkAccounts +Content-Type: application/json +Request Body: + +{ + "accountsDetail":[ + { + "accountId":"11", + "accountType":"LOAN" + }, + { + "accountId":"2", + "accountType":"SAVINGS" + } + ] +} + + +{ + "resourceId": 6 +} + +
+
+ +   +
+
+

Delink accounts from pocket

+

Example Requests:

+
self/pockets?command=delinkAccounts
+
+
+ POST https://DomainName/api/v1/self/pockets?command=delinkAccounts + POST self/pockets?command=delinkAccounts +Content-Type: application/json +Request Body: + +{ "pocketAccountMappingIds" :[8,9] +} + + +{ + "resourceId":10 +} + +
+
+ +   +
+
+

Retrieve accounts linked to pocket

+

All linked loan

+

Example Requests:

+
self/pockets
+
+
+ POST https://DomainName/api/v1/self/pockets + +{ + "loanAccounts": [ + { + "pocketId": 6, + "accountId": 11, + "accountType": 2, + "accountNumber": "000000011", + "id": 10 + } + ], + "savingsAccounts": [ + { + "pocketId": 6, + "accountId": 2, + "accountType": 3, + "accountNumber": "000000002", + "id": 11 + } + ], + "shareAccounts": [] +} + +
+
diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle index 4c292157550..7e59fffdc77 100644 --- a/fineract-provider/build.gradle +++ b/fineract-provider/build.gradle @@ -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/**' ] } diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 3ceb54df6ea..d870c98c21e 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -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'], diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java index 1ae2609f037..fe7b4c2956a 100755 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java @@ -1634,9 +1634,10 @@ public void testSavingsAccountPostInterestWithOverdraft() { 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 { diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java index d560784ca62..1a374c8ce31 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTest.java @@ -108,7 +108,7 @@ public void testSchedulerJobs() throws InterruptedException { 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 jobHistoryData = this.schedulerJobHelper.getSchedulerJobHistory(this.requestSpec, this.responseSpec, jobId.toString()); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java index 2cc98b372f2..64a4d13baff 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/SchedulerJobsTestResults.java @@ -24,6 +24,8 @@ 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 void testApplyAnnualFeeForSavingsJobOutcome() throws InterruptedException 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 void testApplyAnnualFeeForSavingsJobOutcome() throws InterruptedException 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 annualFeeDueDateAsArrayList = (ArrayList) 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)); } diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/client/ClientEntityImportHandlerTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/client/ClientEntityImportHandlerTest.java index ee5e734d196..d1e40486a33 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/client/ClientEntityImportHandlerTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/client/ClientEntityImportHandlerTest.java @@ -37,6 +37,7 @@ 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 void setup() { } @Test + @Ignore public void testClientImport() throws InterruptedException, IOException, ParseException { //in order to populate helper sheets diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/loan/LoanImportHandlerTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/loan/LoanImportHandlerTest.java index 91bb0bdb249..f4fe1771c00 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/loan/LoanImportHandlerTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/loan/LoanImportHandlerTest.java @@ -36,6 +36,7 @@ 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 void setup() { } @Test + @Ignore public void testLoanImport() throws InterruptedException, IOException, ParseException { requestSpec.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); //in order to populate helper sheets diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/office/OfficeImportHandlerTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/office/OfficeImportHandlerTest.java index 9bd80bc8ef9..dab3c5b24e0 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/office/OfficeImportHandlerTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/office/OfficeImportHandlerTest.java @@ -34,6 +34,7 @@ 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 void setup(){ } @Test + @Ignore public void testOfficeImport() throws IOException, InterruptedException, NoSuchFieldException, ParseException { OfficeHelper officeHelper=new OfficeHelper(requestSpec,responseSpec); Workbook workbook=officeHelper.getOfficeWorkBook("dd MMMM yyyy"); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/savings/SavingsImportHandlerTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/savings/SavingsImportHandlerTest.java index c22ec1f2002..66f7bd757a8 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/savings/SavingsImportHandlerTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/bulkimport/importhandler/savings/SavingsImportHandlerTest.java @@ -41,6 +41,7 @@ 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 void setup() { } @Test + @Ignore public void testSavingsImport() throws InterruptedException, IOException, ParseException { requestSpec.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/BatchHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/BatchHelper.java index c99ebeab737..96cf0d09113 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/BatchHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/BatchHelper.java @@ -197,7 +197,7 @@ public static BatchRequest applyLoanRequest(final Long requestId, final Long ref 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," diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java index cb904642e19..8fb7f62ce6d 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java @@ -34,6 +34,7 @@ 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 void testShareAccountApproval() { 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(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index ae53bb78cb0..eb3fe6dce5f 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -23,6 +23,7 @@ import org.apache.fineract.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 CommandWrapperBuilder updateTwoFactorConfiguration() { this.href = "/twofactor/configure"; return this; } + + public CommandWrapperBuilder linkAccountsToPocket() { + this.actionName = PocketApiConstants.linkAccountsActionName; + this.entityName = PocketApiConstants.pocketEntityName; + this.href = "/self/pocket?command="+PocketApiConstants.linkAccountsToPocketCommandParam; + return this; + } + + public CommandWrapperBuilder delinkAccountsFromPocket() { + this.actionName = PocketApiConstants.delinkAccountsActionName; + this.entityName = PocketApiConstants.pocketEntityName; + this.href = "/self/pocket?command="+PocketApiConstants.delinkAccountsFromPocketCommandParam; + return this; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java index 38d1ac9ecfd..affc29e64ff 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/PlatformCacheConfiguration.java @@ -23,6 +23,8 @@ 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 CacheManager cacheManager() { 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; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/SMTPCredentialsData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/SMTPCredentialsData.java index 111b3749681..d6493b876e3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/SMTPCredentialsData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/data/SMTPCredentialsData.java @@ -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 boolean isUseTLS() { return useTLS; } + public String getFromEmail() { + return fromEmail != null ?fromEmail :username; + } + + public String getFromName() { + return fromName != null ?fromName :username; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java index f123c19237d..382f81cca22 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesConstants.java @@ -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 String getValue() { } 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; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java index bf6270850d5..1f18a7d3fed 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/service/ExternalServicesPropertiesReadPlatformServiceImpl.java @@ -75,21 +75,30 @@ public SMTPCredentialsData extractData(final ResultSet rs) throws SQLException, 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); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java index fd0912c38fd..d35a9122db5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java @@ -60,7 +60,6 @@ public void sendToUserAccount(String organisationName, String contactName, 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 void sendDefinedEmail(EmailDetail emailDetails) { 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()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java index 89fe38edb08..d1853870520 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java @@ -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 @@ private Object getBeanObject(final Class classType) throws ClassNotFoundExcep 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()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java index 72d8c49189f..8c1ad54b79f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java @@ -713,7 +713,7 @@ public CommandProcessingResult closeClient(final Long clientId, final JsonComman 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()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 780057915f1..6be781c835d 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -134,7 +134,7 @@ public class LoanCharge extends AbstractPersistableCustom { 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()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java index e205fbf19ed..2fd6c62ea6d 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -134,4 +134,7 @@ LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelated LoanAccountData retrieveLoanByLoanAccount(String loanAccountNumber); Long retrieveLoanIdByAccountNumber(String loanAccountNumber); + + String retrieveAccountNumberByAccountId(Long accountId); + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index e2464d83bbd..20230d6a480 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -2209,4 +2209,12 @@ public Long retrieveLoanIdByAccountNumber(String loanAccountNumber) { } } + public String retrieveAccountNumberByAccountId(Long accountId) { + try { + final String sql = "select loan.account_no from m_loan loan where loan.id = ?"; + return this.jdbcTemplate.queryForObject(sql, new Object[] { accountId }, String.class); + } catch (final EmptyResultDataAccessException e) { + throw new LoanNotFoundException(accountId); + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java index 97427593b74..fc0afcf5077 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java @@ -23,6 +23,8 @@ 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 List findSavingAccountByStatus(@Param("status") Integer s loadLazyCollections(accounts); return accounts ; } + + @Transactional(readOnly=true) + public Page findByStatus(Integer status, Pageable pageable) { + Page accounts = this.repository.findByStatus(status, pageable); + loadLazyCollections(accounts); + return accounts; + } - //Root Entities are enough + //Root Entities are enough public List findByClientIdAndGroupId(@Param("clientId") Long clientId, @Param("groupId") Long groupId) { return this.repository.findByClientIdAndGroupId(clientId, groupId) ; } @@ -118,4 +127,10 @@ private void loadLazyCollections(final List accounts) { } } } + + private void loadLazyCollections(Page accounts) { + for (SavingsAccount account : accounts) { + account.loadLazyCollections(); + } + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java index adac1919bf7..2593297b5bb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java @@ -60,4 +60,6 @@ public interface SavingsAccountReadPlatformService { List retrieveSavingsIdsPendingEscheat(LocalDate tenantLocalDate); boolean isAccountBelongsToClient(final Long clientId, final Long accountId, final DepositAccountType depositAccountType, final String currencyCode) ; + String retrieveAccountNumberByAccountId(Long accountId); + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 6bb4fd1ce4d..b0ba652c7a8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -1248,4 +1248,14 @@ public boolean isAccountBelongsToClient(final Long clientId, final Long accountI * return SavingsAccountAnnualFeeData.instance(id, accountNo, * annualFeeNextDueDate); } } */ + + @Override + public String retrieveAccountNumberByAccountId(Long accountId) { + try { + final String sql = "select s.account_no from m_savings_account s where s.id = ?"; + return this.jdbcTemplate.queryForObject(sql, new Object[] { accountId }, String.class); + } catch (final EmptyResultDataAccessException e) { + throw new SavingsAccountNotFoundException(accountId); + } + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index b421330b95e..1001f1e466b 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -364,9 +364,15 @@ public CommandProcessingResult applyAnnualFee(final Long savingsAccountChargeId, 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()) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java index 16c4266603e..bd41c22b71c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java @@ -27,6 +27,7 @@ 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; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiConstants.java new file mode 100644 index 00000000000..11df8cc21b8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiConstants.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.self.pockets.api; + +public interface PocketApiConstants { + + String pocketsResourceName = "pockets"; + String linkAccountsToPocketCommandParam = "linkAccounts"; + String delinkAccountsFromPocketCommandParam = "delinkAccounts"; + + String linkAccountsActionName = "LINK_ACCOUNT_TO"; + String pocketEntityName = "POCKET"; + String delinkAccountsActionName = "DELINK_ACCOUNT_FROM"; + + String accountIdParamName = "accountId"; + String accountTypeParamName = "accountType"; + String accountsDetail = "accountsDetail"; + String pocketAccountMappingList = "pocketAccountMappingIds"; + String pocketAccountMappingId = "pocketAccountMappingId"; + + String dataValidationMessage = "validation.msg.validation.errors.exist"; + String validationErrorMessage = "Validation errors exist."; + String pocketNotFoundException = "error.msg.pocket.not.found"; + String pocketNotFoundErrorMessage = "Pocket not found."; + String mappingIdNotLinkedToPocketException = "mapping.id.not.linked.to.pocket.exception"; + String mappingIdNotLinkedToPocketErrorMessage = "Mapping Id is not linked to Pocket."; + String uniqueConstraintName = "m_pocket_account_unique_mapping"; + String duplicateMappingException = "error.msg.one.or.more.accounts.are.already.mapped.to.pocket."; + String duplicateMappingExceptionMessage = "One or more accounts are already mapped to pocket."; + String unknownDataIntegrityException = "error.msg.unknown.data.integrity.issue"; + String unknownDataIntegrityExceptionMessage = "Unknown data integrity issue with resource."; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResource.java new file mode 100644 index 00000000000..1d39cc2b456 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/api/PocketApiResource.java @@ -0,0 +1,99 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.self.pockets.api; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.portfolio.self.pockets.service.PocketAccountMappingReadPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/self/pockets") +@Component +@Scope("singleton") +public class PocketApiResource { + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + @SuppressWarnings("rawtypes") + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService; + + @SuppressWarnings("rawtypes") + @Autowired + public PocketApiResource(PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + final DefaultToApiJsonSerializer toApiJsonSerializer, + final PocketAccountMappingReadPlatformService pocketAccountMappingReadPlatformService) { + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.pocketAccountMappingReadPlatformService = pocketAccountMappingReadPlatformService; + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String handleCommands(@QueryParam("command") final String commandParam, @Context final UriInfo uriInfo, + final String apiRequestBodyAsJson) { + + final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); + + CommandProcessingResult result = null; + + if (is(commandParam, PocketApiConstants.linkAccountsToPocketCommandParam)) { + final CommandWrapper commandRequest = builder.linkAccountsToPocket().build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, PocketApiConstants.delinkAccountsFromPocketCommandParam)) { + final CommandWrapper commandRequest = builder.delinkAccountsFromPocket().build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } + + if (result == null) { + throw new UnrecognizedQueryParamException("command", commandParam); + } + + return this.toApiJsonSerializer.serialize(result); + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveAll() { + return this.toApiJsonSerializer.serialize(this.pocketAccountMappingReadPlatformService.retrieveAll()); + } + + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketAccountMappingData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketAccountMappingData.java new file mode 100644 index 00000000000..34b984f63c1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketAccountMappingData.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.self.pockets.data; + +import java.util.Collection; + +import org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMapping; + +@SuppressWarnings("unused") +public class PocketAccountMappingData { + + private final Collection loanAccounts; + private final Collection savingsAccounts; + private final Collection shareAccounts; + + private PocketAccountMappingData(final Collection loanAccounts, Collection savingsAccounts, final Collection shareAccounts ) { + this.loanAccounts = loanAccounts; + this.savingsAccounts = savingsAccounts; + this.shareAccounts = shareAccounts; + } + + public static PocketAccountMappingData instance(final Collection loanAccounts, final Collection savingsAccounts, final Collection shareAccounts) { + + return new PocketAccountMappingData(loanAccounts, savingsAccounts, shareAccounts); + + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketDataValidator.java new file mode 100644 index 00000000000..29d7624209d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/data/PocketDataValidator.java @@ -0,0 +1,140 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.self.pockets.data; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.self.pockets.api.PocketApiConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +@Service +public class PocketDataValidator { + private final Set linkingAccountsSupportedParameters = new HashSet<>( + Arrays.asList(PocketApiConstants.accountIdParamName, PocketApiConstants.accountTypeParamName, + PocketApiConstants.accountsDetail)); + + private final Set delinkingAccountsSupportedParameters = new HashSet<>( + Arrays.asList(PocketApiConstants.pocketAccountMappingList)); + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public PocketDataValidator(FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateForLinkingAccounts(final String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final Type typeOfMap = new TypeToken>() { + }.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.linkingAccountsSupportedParameters); + + final List 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 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>() { + }.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, + this.delinkingAccountsSupportedParameters); + + final List 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 dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(PocketApiConstants.dataValidationMessage, + PocketApiConstants.validationErrorMessage, dataValidationErrors); + } + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/Pocket.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/Pocket.java new file mode 100644 index 00000000000..fe52535d9ca --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/Pocket.java @@ -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 { + + @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); + + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketAccountMapping.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketAccountMapping.java new file mode 100644 index 00000000000..4ee65b9ebda --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketAccountMapping.java @@ -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 { + + @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; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketAccountMappingRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketAccountMappingRepository.java new file mode 100644 index 00000000000..bf58742c0a1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketAccountMappingRepository.java @@ -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, JpaSpecificationExecutor { + + @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 findByPocketId(@Param("pocketId") Long pocketId); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketAccountMappingRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketAccountMappingRepositoryWrapper.java new file mode 100644 index 00000000000..fe32e7ad50d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketAccountMappingRepositoryWrapper.java @@ -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 save(final List pocketAccountsList) { + return this.pocketAccountMappingRepository.save(pocketAccountsList); + } + + public void delete(final List 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 findByPocketId(final Long pocketId) { + return this.pocketAccountMappingRepository.findByPocketId(pocketId); + + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketRepository.java new file mode 100644 index 00000000000..7987a2e755d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketRepository.java @@ -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 , JpaSpecificationExecutor{ + + @Query("select pocket.id from Pocket pocket where pocket.appUserId= :appUserId") + Long findByAppUserId(@Param("appUserId") Long appUserId); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketRepositoryWrapper.java new file mode 100644 index 00000000000..a72b2fde1d3 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/domain/PocketRepositoryWrapper.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.self.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; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/exception/MappingIdNotLinkedToPocketException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/exception/MappingIdNotLinkedToPocketException.java new file mode 100644 index 00000000000..4a331623181 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/exception/MappingIdNotLinkedToPocketException.java @@ -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); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/exception/PocketNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/exception/PocketNotFoundException.java new file mode 100644 index 00000000000..7ae00b41451 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/exception/PocketNotFoundException.java @@ -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); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/handler/DelinkAccountsFromPocketCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/handler/DelinkAccountsFromPocketCommandHandler.java new file mode 100644 index 00000000000..51d76902186 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/handler/DelinkAccountsFromPocketCommandHandler.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.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); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/handler/LinkAccountsToPocketCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/handler/LinkAccountsToPocketCommandHandler.java new file mode 100644 index 00000000000..98ef91b4173 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/handler/LinkAccountsToPocketCommandHandler.java @@ -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); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityService.java new file mode 100644 index 00000000000..18dd1b4f18a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityService.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.self.pockets.service; + +public interface AccountEntityService { + + String getKey(); + + void validateSelfUserAccountMapping(Long accountId); + + String retrieveAccountNumberByAccountId(Long accountId); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceFactory.java new file mode 100644 index 00000000000..5edb17a98c6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceFactory.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.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 accountEntityServiceHashMap = new HashMap<>(); + + @Autowired + public AccountEntityServiceFactory(final Set accountEntityServices) { + for (AccountEntityService service : accountEntityServices) { + this.accountEntityServiceHashMap.put(service.getKey(), service); + } + } + + public AccountEntityService getAccountEntityService(final String key) { + return this.accountEntityServiceHashMap.get(key); + } + + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForLoanImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForLoanImpl.java new file mode 100644 index 00000000000..c4408167c04 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForLoanImpl.java @@ -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); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForSavingsImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForSavingsImpl.java new file mode 100644 index 00000000000..5afbf3abeb0 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForSavingsImpl.java @@ -0,0 +1,70 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.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); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForShareAccountsImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForShareAccountsImpl.java new file mode 100644 index 00000000000..6f0112e966f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/AccountEntityServiceForShareAccountsImpl.java @@ -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); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformService.java new file mode 100644 index 00000000000..7b496aae7aa --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformService.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.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); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformServiceImpl.java new file mode 100644 index 00000000000..b293a29b51b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketAccountMappingReadPlatformServiceImpl.java @@ -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 pocketAccountMappingList = this.pocketAccountMappingRepositoryWrapper + .findByPocketId(pocketId); + if (pocketAccountMappingList != null && !pocketAccountMappingList.isEmpty()) { + Collection loanAccounts = new ArrayList<>(); + Collection savingsAccounts = new ArrayList<>(); + Collection 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; + } + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformService.java new file mode 100644 index 00000000000..9a6649dddfd --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformService.java @@ -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); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformServiceImpl.java new file mode 100644 index 00000000000..ecb8b397a6c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/pockets/service/PocketWritePlatformServiceImpl.java @@ -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 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 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(); + + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformService.java index 06cdf62f574..a6fa93f6be6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformService.java @@ -37,5 +37,6 @@ public interface ShareAccountReadPlatformService extends AccountReadPlatformServ public Set getResponseDataParams(); Collection retrieveAllShareAccountDataForDividends(Long productId, boolean fetchInActiveAccounts, LocalDate startDate); - + + String retrieveAccountNumberByAccountId(Long accountId); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java index 8d9cbee0ef7..1df67559acb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java @@ -38,6 +38,7 @@ 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.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 String schema() { 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); + } + } } diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V350__email_from.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V350__email_from.sql new file mode 100644 index 00000000000..7840e89c63f --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V350__email_from.sql @@ -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'); diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V351__pocket_mapping.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V351__pocket_mapping.sql new file mode 100644 index 00000000000..b97164bfca2 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V351__pocket_mapping.sql @@ -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);