Compare commits
112
Commits
ea2dfd789d
...
uate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7d22382a5 | ||
|
|
8622dcaf90 | ||
|
|
7cdc25322e | ||
|
|
a4c3dbb6c4 | ||
|
|
dd8445de06 | ||
|
|
9345f2294c | ||
|
|
190cd92a85 | ||
|
|
81832b6bb7 | ||
|
|
0bd5296519 | ||
|
|
17e0dab435 | ||
|
|
67810bfaea | ||
|
|
2022dc5241 | ||
|
|
b1cfa996b4 | ||
|
|
02541eed6c | ||
|
|
7c15fbb0c7 | ||
|
|
6e3f195de0 | ||
|
|
a65d801250 | ||
|
|
f45d5890ea | ||
|
|
3c58bb231f | ||
|
|
aabda18b69 | ||
|
|
0e5671b706 | ||
|
|
67214ec0f8 | ||
|
|
026d902199 | ||
|
|
bc30241950 | ||
|
|
f18b5d97d3 | ||
|
|
14df914b9a | ||
|
|
5dc175c5e1 | ||
|
|
cf60515bf6 | ||
|
|
a098f8ba75 | ||
|
|
ab5666c0f4 | ||
|
|
84b1685873 | ||
|
|
b433edfdfe | ||
|
|
383dfdc6e9 | ||
|
|
5ed95c27e8 | ||
|
|
fde670e644 | ||
|
|
3bb7fc8050 | ||
|
|
04d65d857f | ||
|
|
b064ee6307 | ||
|
|
9d09d9ebd4 | ||
|
|
eb88fb0813 | ||
|
|
f13a9c6bba | ||
|
|
0e076889e9 | ||
|
|
87b308b527 | ||
|
|
934a51c9ee | ||
|
|
9980310c9e | ||
|
|
cfcd24efad | ||
|
|
73db4ee114 | ||
|
|
e72b9359c1 | ||
|
|
a1a31aff25 | ||
|
|
2606427203 | ||
|
|
b0bfa386ce | ||
|
|
00d3a2e333 | ||
|
|
048a4ecfde | ||
|
|
7ce7353e7a | ||
|
|
99c153830c | ||
|
|
ba199d78e7 | ||
|
|
99cc972804 | ||
|
|
99a89d5d81 | ||
|
|
388af0a6a3 | ||
|
|
1bd41cff43 | ||
|
|
34f84e1fc6 | ||
|
|
69513f25f3 | ||
|
|
3d30bdb788 | ||
|
|
ed139d6e68 | ||
|
|
db41c2b606 | ||
|
|
fc80869d4b | ||
|
|
8e55b6747c | ||
|
|
21878939b5 | ||
|
|
b5c9101fa4 | ||
|
|
1ff3f9c0c9 | ||
|
|
1eba2c50ed | ||
|
|
3367b7d935 | ||
|
|
5899d2ca07 | ||
|
|
c1e23080fc | ||
|
|
7354da8c1e | ||
|
|
bf5ce421df | ||
|
|
d002fc0c9b | ||
|
|
a6eae3d6e8 | ||
|
|
3e1abaef03 | ||
|
|
40449ef0f7 | ||
|
|
33ea10689f | ||
|
|
8d5b4d1ce9 | ||
|
|
db94056144 | ||
|
|
19639c9e02 | ||
|
|
53b6a94bf8 | ||
|
|
6d0c1fe7f6 | ||
|
|
edaeb674d5 | ||
|
|
2371ad98b1 | ||
|
|
9d944d0196 | ||
|
|
6e31ac9acd | ||
|
|
a051faba5d | ||
|
|
7c4cf6260c | ||
|
|
6bdcac7b77 | ||
|
|
8bf28a8033 | ||
|
|
d4262ebf6f | ||
|
|
656a0b4f13 | ||
|
|
ae7017ca81 | ||
|
|
6413befe07 | ||
|
|
eae5b03d7e | ||
|
|
49d64004cc | ||
|
|
ada24d7497 | ||
|
|
b89e653bcb | ||
|
|
9a30ede278 | ||
|
|
ee915e395a | ||
|
|
7d4f409202 | ||
|
|
2251fd2259 | ||
|
|
45585ecc10 | ||
|
|
7dea88d30f | ||
|
|
d32953ed76 | ||
|
|
5a98a89839 | ||
|
|
d5157ef701 | ||
|
|
c6f130e116 |
@@ -1 +1 @@
|
|||||||
{"sessionId":"0fda6e48-a79d-4980-a9ee-31a6d5f735e1","pid":15548,"acquiredAt":1776168814856}
|
{"sessionId":"b5222d29-c337-4c5d-afde-70cde98cdf46","pid":426448,"acquiredAt":1780052566378}
|
||||||
@@ -52,7 +52,32 @@
|
|||||||
"Bash(git -c commit.gpgsign=false commit -m ' *)",
|
"Bash(git -c commit.gpgsign=false commit -m ' *)",
|
||||||
"Bash(git pull *)",
|
"Bash(git pull *)",
|
||||||
"Bash(./gradlew :fineract-security:compileJava :fineract-provider:compileJava -q)",
|
"Bash(./gradlew :fineract-security:compileJava :fineract-provider:compileJava -q)",
|
||||||
"Bash(git ls-remote *)"
|
"Bash(git ls-remote *)",
|
||||||
|
"Bash(PGPASSWORD=root psql -h localhost -p 5432 -U postgres -d fineract_default -c \"SELECT cv.id, cv.code_value FROM m_code_value cv JOIN m_code c ON cv.code_id=c.id WHERE c.code_name='Constitution' ORDER BY cv.id;\")",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes | %{time_total}s ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-pan -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"panNumber\":\"AQRPV8345E\",\"name\":\"Aditya Vyas\",\"dob\":\"19/12/1988\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes | %{time_total}s ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-pan -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"panNumber\":\"AAMCG5406B\",\"name\":\"GIZMOGLADE PRIVATE LIMITED\",\"dob\":\"01/12/2025\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-pan -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"panNumber\":\"AAMCG5406B\",\"name\":\"\",\"dob\":\"\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-gstn -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"gstNumber\":\"21AAMCG5406BIZ6\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes | %{time_total}s ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-gstn -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"gstNumber\":\"23AADCL9984A1ZH\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"PowerShell(Get-Process | Where-Object { $_.ProcessName -match '^\\(java|gradle\\)' } | Select-Object Id, ProcessName, StartTime, @{n='CPU\\(s\\)';e={[math]::Round\\($_.CPU,1\\)}}, @{n='WS\\(MB\\)';e={[math]::Round\\($_.WorkingSet64/1MB,0\\)}} | Format-Table -AutoSize)",
|
||||||
|
"PowerShell(Get-NetTCPConnection -LocalPort 8443 -ErrorAction SilentlyContinue)",
|
||||||
|
"PowerShell(Stop-Process -Id 3468,3864,39964 -Force)",
|
||||||
|
"PowerShell(Get-Process -Id 3468,3864,39964 -ErrorAction SilentlyContinue)",
|
||||||
|
"PowerShell(foreach \\($id in 3468,3864,39964\\) { try { Stop-Process -Id $id -Force -ErrorAction Stop; \"killed $id\" } catch { \"skip $id - $\\($_.Exception.Message\\)\" } })",
|
||||||
|
"PowerShell(Get-Process java -ErrorAction SilentlyContinue)",
|
||||||
|
"PowerShell(if \\(-not \\(Test-Path .\\\\fineract-provider\\\\logs\\)\\) { New-Item -ItemType Directory -Force .\\\\fineract-provider\\\\logs | Out-Null }; Remove-Item .\\\\fineract-provider\\\\logs\\\\devrun.out -ErrorAction SilentlyContinue; \"ready\")",
|
||||||
|
"PowerShell($j = Get-Process java -ErrorAction SilentlyContinue | Select-Object Id, @{n='CPU\\(s\\)';e={[math]::Round\\($_.CPU,1\\)}}, @{n='WS\\(MB\\)';e={[math]::Round\\($_.WorkingSet64/1MB,0\\)}}, StartTime; $j | Format-Table -AutoSize; \"---\"; Get-NetTCPConnection -LocalPort 8443 -State Listen -ErrorAction SilentlyContinue | Select-Object LocalAddress, OwningProcess)",
|
||||||
|
"PowerShell(if \\(Test-Path .\\\\fineract-provider\\\\logs\\\\devrun.out\\) { $f = Get-Item .\\\\fineract-provider\\\\logs\\\\devrun.out; \"size=$\\($f.Length\\) bytes mtime=$\\($f.LastWriteTime\\)\" } else { \"devrun.out missing\" })",
|
||||||
|
"PowerShell(netstat -ano)",
|
||||||
|
"PowerShell(Get-Process java -ErrorAction SilentlyContinue | Stop-Process -Force; Start-Sleep -Milliseconds 800; $left = Get-Process java -ErrorAction SilentlyContinue; if \\($left\\) { $left | Select-Object Id, ProcessName } else { \"no java processes\" }; Get-NetTCPConnection -LocalPort 8443 -State Listen -ErrorAction SilentlyContinue | Measure-Object | ForEach-Object { \"8443 listeners: $\\($_.Count\\)\" })",
|
||||||
|
"Bash(curl -sk -o /tmp/resp.txt -w \"STATUS=%{http_code}\\\\nCONTENT_TYPE=%{content_type}\\\\n\" -X POST 'https://localhost:8443/fineract-provider/api/v1/authentication' -H 'Content-Type: application/json' -H 'Fineract-Platform-TenantId: default' -d '{\"username\":\"mifos\",\"password\":\"password\"}')",
|
||||||
|
"Bash(curl -sk -o /tmp/r1.txt -w \"STATUS=%{http_code}\\\\n\" -X POST 'https://localhost:8443/fineract-provider/api/v1/authentication' -H 'Content-Type: application/json' -d '{\"username\":\"mifos\",\"password\":\"password\"}')",
|
||||||
|
"Bash(curl -s -o /tmp/r2.txt -w \"STATUS=%{http_code}\\\\n\" -X POST 'http://localhost:4200/fineract-provider/api/v1/authentication' -H 'Content-Type: application/json' -d '{\"username\":\"mifos\",\"password\":\"password\"}')",
|
||||||
|
"Bash(curl -s -o /tmp/r3.txt -w \"STATUS=%{http_code}\\\\n\" -X POST 'http://localhost:4200/fineract-provider/api/v1/authentication' -H 'Content-Type: application/json' -H 'Origin: http://localhost:4200' -H 'Fineract-Platform-TenantId: default' -d '{\"username\":\"mifos\",\"password\":\"password\"}')",
|
||||||
|
"Bash(curl -s -o /tmp/r4.txt -w \"STATUS=%{http_code}\\\\n\" -X OPTIONS 'http://localhost:4200/fineract-provider/api/v1/authentication' -H 'Origin: http://localhost:4200' -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: content-type,fineract-platform-tenantid' -i)",
|
||||||
|
"Bash(git cherry-pick *)",
|
||||||
|
"PowerShell($parent = 'C:\\\\Users\\\\adity\\\\Music\\\\CBS'; if \\(Test-Path $parent\\) { \"parent exists: $parent\"; Get-ChildItem $parent -Directory | Select-Object -ExpandProperty Name } else { \"MISSING parent: $parent\" }; \"---\"; $target = Join-Path $parent 'LincpayEKYC'; if \\(Test-Path $target\\) { \"TARGET ALREADY EXISTS: $target\" } else { \"target free: $target\" })",
|
||||||
|
"Bash(git clone *)"
|
||||||
],
|
],
|
||||||
"additionalDirectories": [
|
"additionalDirectories": [
|
||||||
"c:\\Users\\adity\\Music\\CBS\\cfcfrms-bank-api",
|
"c:\\Users\\adity\\Music\\CBS\\cfcfrms-bank-api",
|
||||||
|
|||||||
+4
-1
@@ -34,4 +34,7 @@ gradleExp/
|
|||||||
.run/
|
.run/
|
||||||
.java-version
|
.java-version
|
||||||
|
|
||||||
.windsurf/
|
.windsurf/
|
||||||
|
|
||||||
|
# Application logs
|
||||||
|
fineract-provider/logs/
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
const https = require('https');
|
||||||
|
const agent = new https.Agent({ rejectUnauthorized: false });
|
||||||
|
|
||||||
|
fetch('https://localhost:8443/fineract-provider/api/v1/authentication?username=mifos&password=password&tenantIdentifier=default', {
|
||||||
|
method: 'POST',
|
||||||
|
agent: agent
|
||||||
|
}).then(res => res.json()).then(data => {
|
||||||
|
const token = data.base64EncodedAuthenticationKey;
|
||||||
|
return fetch('https://localhost:8443/fineract-provider/api/v1/clients/9/accounts?tenantIdentifier=default', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Basic ' + token,
|
||||||
|
'Fineract-Platform-TenantId': 'default'
|
||||||
|
},
|
||||||
|
agent: agent
|
||||||
|
});
|
||||||
|
}).then(res => res.json()).then(data => {
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
}).catch(console.error);
|
||||||
+284
-272
@@ -1,272 +1,284 @@
|
|||||||
/**
|
/**
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
* or more contributor license agreements. See the NOTICE file
|
* or more contributor license agreements. See the NOTICE file
|
||||||
* distributed with this work for additional information
|
* distributed with this work for additional information
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
* to you under the Apache License, Version 2.0 (the
|
* to you under the Apache License, Version 2.0 (the
|
||||||
* "License"); you may not use this file except in compliance
|
* "License"); you may not use this file except in compliance
|
||||||
* with the License. You may obtain a copy of the License at
|
* with the License. You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing,
|
* Unless required by applicable law or agreed to in writing,
|
||||||
* software distributed under the License is distributed on an
|
* software distributed under the License is distributed on an
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
* KIND, either express or implied. See the License for the
|
* KIND, either express or implied. See the License for the
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.fineract.accounting.glaccount.service;
|
package org.apache.fineract.accounting.glaccount.service;
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.accounting.common.AccountingEnumerations;
|
import org.apache.fineract.accounting.common.AccountingEnumerations;
|
||||||
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
|
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
|
||||||
import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
|
import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
|
||||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
||||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
|
import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
|
||||||
import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidClassificationException;
|
import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidClassificationException;
|
||||||
import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException;
|
import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException;
|
||||||
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
|
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
|
||||||
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
import org.springframework.dao.EmptyResultDataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformService {
|
public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformService {
|
||||||
|
|
||||||
private static final String NAME_DECORATED_BASE_ON_HIERARCHY = "concat(substring('........................................', 1, ((LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '.', '')) - 1) * 4)), name)";
|
private static final String NAME_DECORATED_BASE_ON_HIERARCHY = "concat(substring('........................................', 1, ((LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '.', '')) - 1) * 4)), name)";
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
private static final String SQL_AND = " and ";
|
private static final String SQL_AND = " and ";
|
||||||
|
|
||||||
private static final class GLAccountMapper implements RowMapper<GLAccountData> {
|
private static final class GLAccountMapper implements RowMapper<GLAccountData> {
|
||||||
|
|
||||||
private final JournalEntryAssociationParametersData associationParametersData;
|
private final JournalEntryAssociationParametersData associationParametersData;
|
||||||
|
|
||||||
GLAccountMapper(final JournalEntryAssociationParametersData associationParametersData) {
|
GLAccountMapper(final JournalEntryAssociationParametersData associationParametersData) {
|
||||||
if (associationParametersData == null) {
|
if (associationParametersData == null) {
|
||||||
this.associationParametersData = new JournalEntryAssociationParametersData();
|
this.associationParametersData = new JournalEntryAssociationParametersData();
|
||||||
} else {
|
} else {
|
||||||
this.associationParametersData = associationParametersData;
|
this.associationParametersData = associationParametersData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String schema() {
|
public String schema() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(
|
sb.append(
|
||||||
" gl.id as id, name as name, parent_id as parentId, gl_code as glCode, disabled as disabled, manual_journal_entries_allowed as manualEntriesAllowed, ")
|
" gl.id as id, name as name, parent_id as parentId, gl_code as glCode, disabled as disabled, manual_journal_entries_allowed as manualEntriesAllowed, ")
|
||||||
.append("classification_enum as classification, account_usage as accountUsage, gl.description as description, ")
|
.append("classification_enum as classification, account_usage as accountUsage, gl.description as description, ")
|
||||||
.append(NAME_DECORATED_BASE_ON_HIERARCHY).append(" as nameDecorated, ")
|
.append(NAME_DECORATED_BASE_ON_HIERARCHY).append(" as nameDecorated, ")
|
||||||
.append("cv.id as codeId, cv.code_value as codeValue ");
|
.append("cv.id as codeId, cv.code_value as codeValue ");
|
||||||
if (this.associationParametersData.isRunningBalanceRequired()) {
|
if (this.associationParametersData.isRunningBalanceRequired()) {
|
||||||
sb.append(",gl_j.organization_running_balance as organizationRunningBalance ");
|
sb.append(",gl_j.organization_running_balance as organizationRunningBalance ");
|
||||||
}
|
}
|
||||||
sb.append("from acc_gl_account gl left join m_code_value cv on tag_id=cv.id ");
|
sb.append("from acc_gl_account gl left join m_code_value cv on tag_id=cv.id ");
|
||||||
if (this.associationParametersData.isRunningBalanceRequired()) {
|
if (this.associationParametersData.isRunningBalanceRequired()) {
|
||||||
sb.append("left outer Join acc_gl_journal_entry gl_j on gl_j.account_id = gl.id");
|
sb.append("left outer join acc_gl_journal_entry gl_j on gl_j.id = (")
|
||||||
}
|
.append("select id from acc_gl_journal_entry ")
|
||||||
return sb.toString();
|
.append("where account_id = gl.id ")
|
||||||
}
|
.append("order by id desc limit 1) ");
|
||||||
|
}
|
||||||
@Override
|
return sb.toString();
|
||||||
public GLAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
}
|
||||||
|
|
||||||
final Long id = rs.getLong("id");
|
@Override
|
||||||
final String name = rs.getString("name");
|
public GLAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||||
final Long parentId = JdbcSupport.getLong(rs, "parentId");
|
|
||||||
final String glCode = rs.getString("glCode");
|
final Long id = rs.getLong("id");
|
||||||
final boolean disabled = rs.getBoolean("disabled");
|
final String name = rs.getString("name");
|
||||||
final boolean manualEntriesAllowed = rs.getBoolean("manualEntriesAllowed");
|
final Long parentId = JdbcSupport.getLong(rs, "parentId");
|
||||||
final int accountTypeId = JdbcSupport.getInteger(rs, "classification");
|
final String glCode = rs.getString("glCode");
|
||||||
final EnumOptionData accountType = AccountingEnumerations.gLAccountType(accountTypeId);
|
final boolean disabled = rs.getBoolean("disabled");
|
||||||
final int usageId = JdbcSupport.getInteger(rs, "accountUsage");
|
final boolean manualEntriesAllowed = rs.getBoolean("manualEntriesAllowed");
|
||||||
final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(usageId);
|
final int accountTypeId = JdbcSupport.getInteger(rs, "classification");
|
||||||
final String description = rs.getString("description");
|
final EnumOptionData accountType = AccountingEnumerations.gLAccountType(accountTypeId);
|
||||||
final String nameDecorated = rs.getString("nameDecorated");
|
final int usageId = JdbcSupport.getInteger(rs, "accountUsage");
|
||||||
final Long codeId = rs.wasNull() ? null : rs.getLong("codeId");
|
final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(usageId);
|
||||||
final String codeValue = rs.getString("codeValue");
|
final String description = rs.getString("description");
|
||||||
final CodeValueData tagId = CodeValueData.instance(codeId, codeValue);
|
final String nameDecorated = rs.getString("nameDecorated");
|
||||||
Long organizationRunningBalance = null;
|
final Long codeId = rs.wasNull() ? null : rs.getLong("codeId");
|
||||||
if (associationParametersData.isRunningBalanceRequired()) {
|
final String codeValue = rs.getString("codeValue");
|
||||||
organizationRunningBalance = rs.getLong("organizationRunningBalance");
|
final CodeValueData tagId = CodeValueData.instance(codeId, codeValue);
|
||||||
}
|
Long organizationRunningBalance = null;
|
||||||
return new GLAccountData().setId(id).setName(name).setParentId(parentId).setGlCode(glCode).setDisabled(disabled)
|
if (associationParametersData.isRunningBalanceRequired()) {
|
||||||
.setManualEntriesAllowed(manualEntriesAllowed).setType(accountType).setUsage(usage).setDescription(description)
|
organizationRunningBalance = rs.getLong("organizationRunningBalance");
|
||||||
.setNameDecorated(nameDecorated).setTagId(tagId).setOrganizationRunningBalance(organizationRunningBalance);
|
}
|
||||||
}
|
return new GLAccountData().setId(id).setName(name).setParentId(parentId).setGlCode(glCode).setDisabled(disabled)
|
||||||
}
|
.setManualEntriesAllowed(manualEntriesAllowed).setType(accountType).setUsage(usage).setDescription(description)
|
||||||
|
.setNameDecorated(nameDecorated).setTagId(tagId).setOrganizationRunningBalance(organizationRunningBalance);
|
||||||
@Override
|
}
|
||||||
public List<GLAccountData> retrieveAllGLAccounts(final Integer accountClassification, final String searchParam, final Integer usage,
|
}
|
||||||
final Boolean manualTransactionsAllowed, final Boolean disabled,
|
|
||||||
JournalEntryAssociationParametersData associationParametersData) {
|
@Override
|
||||||
if (accountClassification != null && !checkValidGLAccountType(accountClassification)) {
|
public List<GLAccountData> retrieveAllGLAccounts(final Integer accountClassification, final String searchParam, final Integer usage,
|
||||||
throw new GLAccountInvalidClassificationException(accountClassification);
|
final Boolean manualTransactionsAllowed, final Boolean disabled,
|
||||||
}
|
JournalEntryAssociationParametersData associationParametersData) {
|
||||||
|
if (accountClassification != null && !checkValidGLAccountType(accountClassification)) {
|
||||||
if (usage != null && !checkValidGLAccountUsage(usage)) {
|
throw new GLAccountInvalidClassificationException(accountClassification);
|
||||||
throw new GLAccountInvalidClassificationException(accountClassification);
|
}
|
||||||
}
|
|
||||||
|
if (usage != null && !checkValidGLAccountUsage(usage)) {
|
||||||
final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
|
throw new GLAccountInvalidClassificationException(accountClassification);
|
||||||
String sql = "select " + rm.schema();
|
}
|
||||||
// append SQL statement for fetching account totals
|
|
||||||
if (associationParametersData != null && associationParametersData.isRunningBalanceRequired()) {
|
final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
|
||||||
sql = sql + " and gl_j.id in (select t1.id from (select t2.account_id, max(t2.id) as id from "
|
String sql = "select " + rm.schema();
|
||||||
+ "(select id, max(entry_date) as entry_date, account_id from acc_gl_journal_entry where is_running_balance_calculated = true "
|
// append SQL statement for fetching account totals
|
||||||
+ "group by account_id desc, id) t3 inner join acc_gl_journal_entry t2 on t2.account_id = t3.account_id and t2.entry_date = t3.entry_date "
|
if (associationParametersData != null && associationParametersData.isRunningBalanceRequired()) {
|
||||||
+ "group by t2.account_id desc) t1)";
|
sql = sql + " and gl_j.id in (select t1.id from (select t2.account_id, max(t2.id) as id from "
|
||||||
}
|
+ "(select id, max(entry_date) as entry_date, account_id from acc_gl_journal_entry where is_running_balance_calculated = true "
|
||||||
final Object[] parameterArray = new Object[3];
|
+ "group by account_id desc, id) t3 inner join acc_gl_journal_entry t2 on t2.account_id = t3.account_id and t2.entry_date = t3.entry_date "
|
||||||
int arrayPos = 0;
|
+ "group by t2.account_id desc) t1)";
|
||||||
boolean filtersPresent = false;
|
}
|
||||||
if ((accountClassification != null) || StringUtils.isNotBlank(searchParam) || (usage != null) || (manualTransactionsAllowed != null)
|
final Object[] parameterArray = new Object[3];
|
||||||
|| (disabled != null)) {
|
int arrayPos = 0;
|
||||||
filtersPresent = true;
|
boolean filtersPresent = false;
|
||||||
sql += " where";
|
if ((accountClassification != null) || StringUtils.isNotBlank(searchParam) || (usage != null) || (manualTransactionsAllowed != null)
|
||||||
}
|
|| (disabled != null)) {
|
||||||
|
filtersPresent = true;
|
||||||
if (filtersPresent) {
|
sql += " where";
|
||||||
boolean firstWhereConditionAdded = false;
|
}
|
||||||
if (accountClassification != null) {
|
|
||||||
sql += " classification_enum = ?";
|
if (filtersPresent) {
|
||||||
parameterArray[arrayPos] = accountClassification.shortValue();
|
boolean firstWhereConditionAdded = false;
|
||||||
arrayPos = arrayPos + 1;
|
if (accountClassification != null) {
|
||||||
firstWhereConditionAdded = true;
|
sql += " classification_enum = ?";
|
||||||
}
|
parameterArray[arrayPos] = accountClassification.shortValue();
|
||||||
if (StringUtils.isNotBlank(searchParam)) {
|
arrayPos = arrayPos + 1;
|
||||||
if (firstWhereConditionAdded) {
|
firstWhereConditionAdded = true;
|
||||||
sql += SQL_AND;
|
}
|
||||||
}
|
if (StringUtils.isNotBlank(searchParam)) {
|
||||||
sql += " ( name like %?% or gl_code like %?% )";
|
if (firstWhereConditionAdded) {
|
||||||
parameterArray[arrayPos] = searchParam;
|
sql += SQL_AND;
|
||||||
arrayPos = arrayPos + 1;
|
}
|
||||||
parameterArray[arrayPos] = searchParam;
|
sql += " ( name like ? or gl_code like ? )";
|
||||||
arrayPos = arrayPos + 1;
|
parameterArray[arrayPos] = "%" + searchParam + "%";
|
||||||
firstWhereConditionAdded = true;
|
arrayPos = arrayPos + 1;
|
||||||
}
|
parameterArray[arrayPos] = "%" + searchParam + "%";
|
||||||
if (usage != null) {
|
arrayPos = arrayPos + 1;
|
||||||
if (firstWhereConditionAdded) {
|
firstWhereConditionAdded = true;
|
||||||
sql += SQL_AND;
|
}
|
||||||
}
|
if (usage != null) {
|
||||||
if (GLAccountUsage.HEADER.getValue().equals(usage)) {
|
if (firstWhereConditionAdded) {
|
||||||
sql += " account_usage = 2 ";
|
sql += SQL_AND;
|
||||||
} else if (GLAccountUsage.DETAIL.getValue().equals(usage)) {
|
}
|
||||||
sql += " account_usage = 1 ";
|
if (GLAccountUsage.HEADER.getValue().equals(usage)) {
|
||||||
}
|
sql += " account_usage = 2 ";
|
||||||
firstWhereConditionAdded = true;
|
} else if (GLAccountUsage.DETAIL.getValue().equals(usage)) {
|
||||||
}
|
sql += " account_usage = 1 ";
|
||||||
if (manualTransactionsAllowed != null) {
|
}
|
||||||
if (firstWhereConditionAdded) {
|
firstWhereConditionAdded = true;
|
||||||
sql += SQL_AND;
|
}
|
||||||
}
|
if (manualTransactionsAllowed != null) {
|
||||||
|
if (firstWhereConditionAdded) {
|
||||||
sql += " manual_journal_entries_allowed = " + manualTransactionsAllowed;
|
sql += SQL_AND;
|
||||||
firstWhereConditionAdded = true;
|
}
|
||||||
}
|
|
||||||
if (disabled != null) {
|
sql += " manual_journal_entries_allowed = " + manualTransactionsAllowed;
|
||||||
if (firstWhereConditionAdded) {
|
firstWhereConditionAdded = true;
|
||||||
sql += SQL_AND;
|
}
|
||||||
}
|
if (disabled != null) {
|
||||||
|
if (firstWhereConditionAdded) {
|
||||||
sql += " disabled = " + disabled;
|
sql += SQL_AND;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
sql += " disabled = " + disabled;
|
||||||
sql += " ORDER BY gl_code ASC";
|
}
|
||||||
|
}
|
||||||
final Object[] finalObjectArray = Arrays.copyOf(parameterArray, arrayPos);
|
|
||||||
return this.jdbcTemplate.query(sql, rm, (Object[]) finalObjectArray);// NOSONAR
|
sql += " ORDER BY gl_code ASC";
|
||||||
}
|
|
||||||
|
final Object[] finalObjectArray = Arrays.copyOf(parameterArray, arrayPos);
|
||||||
@Override
|
return this.jdbcTemplate.query(sql, rm, (Object[]) finalObjectArray);// NOSONAR
|
||||||
public GLAccountData retrieveGLAccountById(final long glAccountId, JournalEntryAssociationParametersData associationParametersData) {
|
}
|
||||||
try {
|
|
||||||
final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
|
@Override
|
||||||
final StringBuilder sql = new StringBuilder();
|
public GLAccountData retrieveGLAccountById(final long glAccountId, JournalEntryAssociationParametersData associationParametersData) {
|
||||||
sql.append("select ").append(rm.schema());
|
try {
|
||||||
sql.append(" where gl.id = ?");
|
final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
|
||||||
if (associationParametersData.isRunningBalanceRequired()) {
|
final StringBuilder sql = new StringBuilder();
|
||||||
sql.append(" and gl_j.is_running_balance_calculated = true ")
|
sql.append("select ").append(rm.schema());
|
||||||
.append(" ORDER BY gl_j.entry_date DESC,gl_j.id DESC LIMIT 1");
|
sql.append(" where gl.id = ?");
|
||||||
}
|
|
||||||
|
final GLAccountData glAccount = this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
|
||||||
return this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
|
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
// Live balance: sum all non-reversed journal entries so cashier sees real-time balance
|
||||||
throw new GLAccountNotFoundException(glAccountId, e);
|
// without waiting for the "Update Accounting Running Balances" scheduled job.
|
||||||
}
|
if (associationParametersData != null && associationParametersData.isRunningBalanceRequired() && glAccount != null) {
|
||||||
}
|
final String liveBalanceSql = "SELECT COALESCE(ABS(SUM(CASE WHEN type_enum = 2 THEN amount ELSE -amount END)), 0) "
|
||||||
|
+ "FROM acc_gl_journal_entry "
|
||||||
@Override
|
+ "WHERE account_id = ? AND (reversed = false OR reversed IS NULL)";
|
||||||
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts(final GLAccountType accountType) {
|
final java.math.BigDecimal liveBalance = this.jdbcTemplate.queryForObject(liveBalanceSql,
|
||||||
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.DETAIL.getValue(), null, false,
|
java.math.BigDecimal.class, glAccountId);
|
||||||
new JournalEntryAssociationParametersData());
|
glAccount.setOrganizationRunningBalance(liveBalance != null ? liveBalance.longValue() : 0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
return glAccount;
|
||||||
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts() {
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
return retrieveAllGLAccounts(null, null, GLAccountUsage.DETAIL.getValue(), null, false,
|
throw new GLAccountNotFoundException(glAccountId, e);
|
||||||
new JournalEntryAssociationParametersData());
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean checkValidGLAccountType(final int type) {
|
@Override
|
||||||
for (final GLAccountType accountType : GLAccountType.values()) {
|
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts(final GLAccountType accountType) {
|
||||||
if (accountType.getValue().equals(type)) {
|
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.DETAIL.getValue(), null, false,
|
||||||
return true;
|
new JournalEntryAssociationParametersData());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
@Override
|
||||||
}
|
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts() {
|
||||||
|
return retrieveAllGLAccounts(null, null, GLAccountUsage.DETAIL.getValue(), null, false,
|
||||||
private static boolean checkValidGLAccountUsage(final int type) {
|
new JournalEntryAssociationParametersData());
|
||||||
for (final GLAccountUsage accountUsage : GLAccountUsage.values()) {
|
}
|
||||||
if (accountUsage.getValue().equals(type)) {
|
|
||||||
return true;
|
private static boolean checkValidGLAccountType(final int type) {
|
||||||
}
|
for (final GLAccountType accountType : GLAccountType.values()) {
|
||||||
}
|
if (accountType.getValue().equals(type)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
return false;
|
||||||
public GLAccountData retrieveNewGLAccountDetails(final Integer type) {
|
}
|
||||||
return GLAccountData.sensibleDefaultsForNewGLAccountCreation(type);
|
|
||||||
}
|
private static boolean checkValidGLAccountUsage(final int type) {
|
||||||
|
for (final GLAccountUsage accountUsage : GLAccountUsage.values()) {
|
||||||
@Override
|
if (accountUsage.getValue().equals(type)) {
|
||||||
public List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(final GLAccountType accountType) {
|
return true;
|
||||||
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.HEADER.getValue(), null, false,
|
}
|
||||||
new JournalEntryAssociationParametersData());
|
}
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType) {
|
@Override
|
||||||
final GLAccountDataLookUpMapper mapper = new GLAccountDataLookUpMapper();
|
public GLAccountData retrieveNewGLAccountDetails(final Integer type) {
|
||||||
final String sql = "Select " + GLAccountDataLookUpMapper.LOOKUP_SCHEMA + " where rule.id=? and tags.acc_type_enum=?";
|
return GLAccountData.sensibleDefaultsForNewGLAccountCreation(type);
|
||||||
return this.jdbcTemplate.query(sql, mapper, (Object[]) new Object[] { ruleId, transactionType });// NOSONAR
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
private static final class GLAccountDataLookUpMapper implements RowMapper<GLAccountDataForLookup> {
|
public List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(final GLAccountType accountType) {
|
||||||
|
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.HEADER.getValue(), null, false,
|
||||||
private static final String LOOKUP_SCHEMA = " gl.id as id, gl.name as name, gl.gl_code as glCode from acc_accounting_rule rule join acc_rule_tags tags on tags.acc_rule_id = rule.id join acc_gl_account gl on gl.tag_id=tags.tag_id";
|
new JournalEntryAssociationParametersData());
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public GLAccountDataForLookup mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
@Override
|
||||||
final Long id = JdbcSupport.getLong(rs, "id");
|
public List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType) {
|
||||||
final String name = rs.getString("name");
|
final GLAccountDataLookUpMapper mapper = new GLAccountDataLookUpMapper();
|
||||||
final String glCode = rs.getString("glCode");
|
final String sql = "Select " + GLAccountDataLookUpMapper.LOOKUP_SCHEMA + " where rule.id=? and tags.acc_type_enum=?";
|
||||||
|
return this.jdbcTemplate.query(sql, mapper, (Object[]) new Object[] { ruleId, transactionType });// NOSONAR
|
||||||
return new GLAccountDataForLookup().setId(id).setName(name).setGlCode(glCode);
|
}
|
||||||
}
|
|
||||||
}
|
private static final class GLAccountDataLookUpMapper implements RowMapper<GLAccountDataForLookup> {
|
||||||
}
|
|
||||||
|
private static final String LOOKUP_SCHEMA = " gl.id as id, gl.name as name, gl.gl_code as glCode from acc_accounting_rule rule join acc_rule_tags tags on tags.acc_rule_id = rule.id join acc_gl_account gl on gl.tag_id=tags.tag_id";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GLAccountDataForLookup mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||||
|
final Long id = JdbcSupport.getLong(rs, "id");
|
||||||
|
final String name = rs.getString("name");
|
||||||
|
final String glCode = rs.getString("glCode");
|
||||||
|
|
||||||
|
return new GLAccountDataForLookup().setId(id).setName(name).setGlCode(glCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
-1
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.accounting.journalentry;
|
package org.apache.fineract.accounting.journalentry;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
||||||
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
|
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
|
||||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
|
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
|
||||||
|
|||||||
+14
-14
@@ -277,14 +277,14 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayFragmentName, element);
|
final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayFragmentName, element);
|
||||||
/**
|
/*
|
||||||
* Variable stores a map representation of charges (key) and their associated income Id's (value) extracted from
|
* Variable stores a map representation of charges (key) and their associated income Id's (value) extracted from
|
||||||
* the passed in Jsoncommand
|
* the passed in Jsoncommand
|
||||||
**/
|
*/
|
||||||
final Map<Long, Long> inputChargeToIncomeAccountMap = new HashMap<>();
|
final Map<Long, Long> inputChargeToIncomeAccountMap = new HashMap<>();
|
||||||
/***
|
/*
|
||||||
* Variable stores all charges which have already been mapped to Income Accounts in the system
|
* Variable stores all charges which have already been mapped to Income Accounts in the system
|
||||||
**/
|
*/
|
||||||
final Set<Long> existingCharges = new HashSet<>();
|
final Set<Long> existingCharges = new HashSet<>();
|
||||||
if (chargeToIncomeAccountMappingArray != null) {
|
if (chargeToIncomeAccountMappingArray != null) {
|
||||||
if (changes != null) {
|
if (changes != null) {
|
||||||
@@ -301,13 +301,13 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
// If input map is empty, delete all existing mappings
|
// If input map is empty, delete all existing mappings
|
||||||
if (inputChargeToIncomeAccountMap.size() == 0) {
|
if (inputChargeToIncomeAccountMap.size() == 0) {
|
||||||
this.accountMappingRepository.deleteAll(existingChargeToIncomeAccountMappings);
|
this.accountMappingRepository.deleteAll(existingChargeToIncomeAccountMappings);
|
||||||
} /**
|
} /*
|
||||||
* Else, <br/>
|
* Else, <br/>
|
||||||
* update existing mappings OR <br/>
|
* update existing mappings OR <br/>
|
||||||
* delete old mappings (which are already present, but not passed in as a part of Jsoncommand)<br/>
|
* delete old mappings (which are already present, but not passed in as a part of Jsoncommand)<br/>
|
||||||
* Create new mappings for charges that are passed in as a part of the Jsoncommand but not already present
|
* Create new mappings for charges that are passed in as a part of the Jsoncommand but not already present
|
||||||
*
|
*
|
||||||
**/
|
*/
|
||||||
else {
|
else {
|
||||||
for (final ProductToGLAccountMapping chargeToIncomeAccountMapping : existingChargeToIncomeAccountMappings) {
|
for (final ProductToGLAccountMapping chargeToIncomeAccountMapping : existingChargeToIncomeAccountMappings) {
|
||||||
final Long currentCharge = chargeToIncomeAccountMapping.getCharge().getId();
|
final Long currentCharge = chargeToIncomeAccountMapping.getCharge().getId();
|
||||||
@@ -356,14 +356,14 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
.findAllPaymentTypeToFundSourceMappings(productId, portfolioProductType.getValue());
|
.findAllPaymentTypeToFundSourceMappings(productId, portfolioProductType.getValue());
|
||||||
final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper
|
final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper
|
||||||
.extractJsonArrayNamed(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
|
.extractJsonArrayNamed(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
|
||||||
/**
|
/*
|
||||||
* Variable stores a map representation of Payment channels (key) and their fund sources (value) extracted from
|
* Variable stores a map representation of Payment channels (key) and their fund sources (value) extracted from
|
||||||
* the passed in Jsoncommand
|
* the passed in Jsoncommand
|
||||||
**/
|
*/
|
||||||
final Map<Long, Long> inputPaymentChannelFundSourceMap = new HashMap<>();
|
final Map<Long, Long> inputPaymentChannelFundSourceMap = new HashMap<>();
|
||||||
/***
|
/*
|
||||||
* Variable stores all payment types which have already been mapped to Fund Sources in the system
|
* Variable stores all payment types which have already been mapped to Fund Sources in the system
|
||||||
**/
|
*/
|
||||||
final Set<Long> existingPaymentTypes = new HashSet<>();
|
final Set<Long> existingPaymentTypes = new HashSet<>();
|
||||||
if (paymentChannelMappingArray != null) {
|
if (paymentChannelMappingArray != null) {
|
||||||
if (changes != null) {
|
if (changes != null) {
|
||||||
@@ -381,14 +381,14 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
// If input map is empty, delete all existing mappings
|
// If input map is empty, delete all existing mappings
|
||||||
if (inputPaymentChannelFundSourceMap.isEmpty()) {
|
if (inputPaymentChannelFundSourceMap.isEmpty()) {
|
||||||
this.accountMappingRepository.deleteAll(existingPaymentChannelToFundSourceMappings);
|
this.accountMappingRepository.deleteAll(existingPaymentChannelToFundSourceMappings);
|
||||||
} /**
|
} /*
|
||||||
* Else, <br/>
|
* Else, <br/>
|
||||||
* update existing mappings OR <br/>
|
* update existing mappings OR <br/>
|
||||||
* delete old mappings (which re already present, but not passed in as a part of Jsoncommand)<br/>
|
* delete old mappings (which re already present, but not passed in as a part of Jsoncommand)<br/>
|
||||||
* Create new mappings for payment types that are passed in as a part of the Jsoncommand but not already
|
* Create new mappings for payment types that are passed in as a part of the Jsoncommand but not already
|
||||||
* present
|
* present
|
||||||
*
|
*
|
||||||
**/
|
*/
|
||||||
else {
|
else {
|
||||||
for (final ProductToGLAccountMapping existingPaymentChannelToFundSourceMapping : existingPaymentChannelToFundSourceMappings) {
|
for (final ProductToGLAccountMapping existingPaymentChannelToFundSourceMapping : existingPaymentChannelToFundSourceMappings) {
|
||||||
final Long currentPaymentChannelId = existingPaymentChannelToFundSourceMapping.getPaymentType().getId();
|
final Long currentPaymentChannelId = existingPaymentChannelToFundSourceMapping.getPaymentType().getId();
|
||||||
@@ -584,9 +584,9 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
// based on input condition
|
// based on input condition
|
||||||
|
|
||||||
GLAccount glAccount;
|
GLAccount glAccount;
|
||||||
/**
|
/*
|
||||||
* Both CASH and Accrual placeholders have the same value for income from Interest and penalties
|
* Both CASH and Accrual placeholders have the same value for income from Interest and penalties
|
||||||
**/
|
*/
|
||||||
CashAccountsForLoan placeHolderAccountType;
|
CashAccountsForLoan placeHolderAccountType;
|
||||||
if (isPenalty) {
|
if (isPenalty) {
|
||||||
glAccount = getAccountByIdAndType(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), GLAccountType.INCOME,
|
glAccount = getAccountByIdAndType(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), GLAccountType.INCOME,
|
||||||
|
|||||||
+2
-2
@@ -181,12 +181,12 @@ public class TellerApiResource {
|
|||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Operation(summary = "Create Cashiers", description = """
|
@Operation(summary = "Create Cashiers", description = """
|
||||||
Mandatory Fields:\s
|
Mandatory Fields:
|
||||||
Cashier/staff, Fromm Date, To Date, Full Day or From time and To time
|
Cashier/staff, Fromm Date, To Date, Full Day or From time and To time
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Optional Fields:\s
|
Optional Fields:
|
||||||
Description/Notes""")
|
Description/Notes""")
|
||||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = TellerApiResourceSwagger.PostTellersTellerIdCashiersRequest.class)))
|
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = TellerApiResourceSwagger.PostTellersTellerIdCashiersRequest.class)))
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = TellerApiResourceSwagger.PostTellersTellerIdCashiersResponse.class)))
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = TellerApiResourceSwagger.PostTellersTellerIdCashiersResponse.class)))
|
||||||
|
|||||||
+11
@@ -76,6 +76,9 @@ public class Teller extends AbstractPersistableCustom<Long> {
|
|||||||
@Column(name = "state", nullable = false)
|
@Column(name = "state", nullable = false)
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
@Column(name = "is_deleted", nullable = false)
|
||||||
|
private boolean deleted = false;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "teller", fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "teller", fetch = FetchType.LAZY)
|
||||||
private Set<Cashier> cashiers;
|
private Set<Cashier> cashiers;
|
||||||
|
|
||||||
@@ -161,4 +164,12 @@ public class Teller extends AbstractPersistableCustom<Long> {
|
|||||||
this.office.getId();
|
this.office.getId();
|
||||||
this.cashiers.size();
|
this.cashiers.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void delete() {
|
||||||
|
this.deleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleted() {
|
||||||
|
return this.deleted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -31,4 +31,6 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
|||||||
public interface TellerRepository extends JpaRepository<Teller, Long>, JpaSpecificationExecutor<Teller> {
|
public interface TellerRepository extends JpaRepository<Teller, Long>, JpaSpecificationExecutor<Teller> {
|
||||||
|
|
||||||
Collection<Teller> findTellerByOfficeId(Long officeId);
|
Collection<Teller> findTellerByOfficeId(Long officeId);
|
||||||
|
|
||||||
|
long countByDeletedFalse();
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -36,6 +36,9 @@ public class TellerRepositoryWrapper {
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Teller findOneWithNotFoundDetection(final Long id) {
|
public Teller findOneWithNotFoundDetection(final Long id) {
|
||||||
final Teller teller = this.repository.findById(id).orElseThrow(() -> new TellerNotFoundException(id));
|
final Teller teller = this.repository.findById(id).orElseThrow(() -> new TellerNotFoundException(id));
|
||||||
|
if (teller.isDeleted()) {
|
||||||
|
throw new TellerNotFoundException(id);
|
||||||
|
}
|
||||||
teller.initializeLazyCollections();
|
teller.initializeLazyCollections();
|
||||||
return teller;
|
return teller;
|
||||||
}
|
}
|
||||||
@@ -51,4 +54,8 @@ public class TellerRepositoryWrapper {
|
|||||||
public void delete(final Teller teller) {
|
public void delete(final Teller teller) {
|
||||||
this.repository.delete(teller);
|
this.repository.delete(teller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean existsAny() {
|
||||||
|
return this.repository.countByDeletedFalse() > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
@@ -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.organisation.teller.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class TellerAlreadyExistsException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE_CODE = "error.msg.teller.already.exists";
|
||||||
|
private static final String DEFAULT_ERROR_MESSAGE = "Cannot create teller as a teller is already present.";
|
||||||
|
|
||||||
|
public TellerAlreadyExistsException() {
|
||||||
|
super(ERROR_MESSAGE_CODE, DEFAULT_ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-1
@@ -34,7 +34,8 @@ public enum CommandProcessingResultType {
|
|||||||
AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
|
AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
|
||||||
REJECTED(3, "commandProcessingResultType.rejected"), //
|
REJECTED(3, "commandProcessingResultType.rejected"), //
|
||||||
UNDER_PROCESSING(4, "commandProcessingResultType.underProcessing"), //
|
UNDER_PROCESSING(4, "commandProcessingResultType.underProcessing"), //
|
||||||
ERROR(5, "commandProcessingResultType.error");
|
ERROR(5, "commandProcessingResultType.error"), //
|
||||||
|
TERMINATE(6, "commandProcessingResultType.terminate");
|
||||||
|
|
||||||
private static final Map<Integer, CommandProcessingResultType> BY_ID = Arrays.stream(values())
|
private static final Map<Integer, CommandProcessingResultType> BY_ID = Arrays.stream(values())
|
||||||
.collect(Collectors.toMap(CommandProcessingResultType::getValue, v -> v));
|
.collect(Collectors.toMap(CommandProcessingResultType::getValue, v -> v));
|
||||||
@@ -59,4 +60,8 @@ public enum CommandProcessingResultType {
|
|||||||
public boolean isRejected() {
|
public boolean isRejected() {
|
||||||
return this == REJECTED;
|
return this == REJECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTerminate() {
|
||||||
|
return this == TERMINATE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+355
-1
@@ -71,6 +71,344 @@ public class CommandWrapperBuilder {
|
|||||||
this.sanitizeJsonKeys);
|
this.sanitizeJsonKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createChequeSeries() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CHEQUESERIES";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/chequeseries";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder updateChequeSeries(final Long seriesId) {
|
||||||
|
this.actionName = "UPDATE";
|
||||||
|
this.entityName = "CHEQUESERIES";
|
||||||
|
this.entityId = seriesId;
|
||||||
|
this.href = "/chequeseries/" + seriesId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder deleteChequeSeries(final Long seriesId) {
|
||||||
|
this.actionName = "DELETE";
|
||||||
|
this.entityName = "CHEQUESERIES";
|
||||||
|
this.entityId = seriesId;
|
||||||
|
this.href = "/chequeseries/" + seriesId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createChequeStockLot() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CHEQUESTOCKLOT";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/chequestocklots";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder reportLostChequeStockLot(final Long lotId) {
|
||||||
|
this.actionName = "REPORTLOST";
|
||||||
|
this.entityName = "CHEQUESTOCKLOT";
|
||||||
|
this.entityId = lotId;
|
||||||
|
this.href = "/chequestocklots/" + lotId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createChequeStockTransfer() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CHEQUESTOCKTRANSFER";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/chequestocktransfers";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder receiveChequeStockTransfer(final Long transferId) {
|
||||||
|
this.actionName = "RECEIVE";
|
||||||
|
this.entityName = "CHEQUESTOCKTRANSFER";
|
||||||
|
this.entityId = transferId;
|
||||||
|
this.href = "/chequestocktransfers/" + transferId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder rejectChequeStockTransfer(final Long transferId) {
|
||||||
|
this.actionName = "REJECT";
|
||||||
|
this.entityName = "CHEQUESTOCKTRANSFER";
|
||||||
|
this.entityId = transferId;
|
||||||
|
this.href = "/chequestocktransfers/" + transferId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createChequeBook() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/chequebooks";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder issueChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "ISSUE";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder rejectChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "REJECT";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder withdrawChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "WITHDRAW";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder stopPaymentChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "STOPPAYMENT";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder revokeStopPaymentChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "REVOKESTOPPAYMENT";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder reportLostChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "REPORTLOST";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder cancelLeafChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "CANCELLEAF";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder markUsedChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "MARKUSED";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder returnChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "RETURN";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder approveChequeBookPendingRequest(final Long pendingRequestId) {
|
||||||
|
this.actionName = "APPROVE";
|
||||||
|
this.entityName = "CHEQUEBOOKPENDINGREQUEST";
|
||||||
|
this.entityId = pendingRequestId;
|
||||||
|
this.href = "/chequebook/pending-requests/" + pendingRequestId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder rejectChequeBookPendingRequest(final Long pendingRequestId) {
|
||||||
|
this.actionName = "REJECT";
|
||||||
|
this.entityName = "CHEQUEBOOKPENDINGREQUEST";
|
||||||
|
this.entityId = pendingRequestId;
|
||||||
|
this.href = "/chequebook/pending-requests/" + pendingRequestId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Clearing (CTS + NACH) command wrappers =====
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createClearingSettlementBatch() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CLEARINGSETTLEMENTBATCH";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/clearing/settlement-batches";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder postClearingSettlementBatch(final Long settlementBatchId) {
|
||||||
|
this.actionName = "POST";
|
||||||
|
this.entityName = "CLEARINGSETTLEMENTBATCH";
|
||||||
|
this.entityId = settlementBatchId;
|
||||||
|
this.href = "/clearing/settlement-batches/" + settlementBatchId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder rejectClearingSettlementBatch(final Long settlementBatchId) {
|
||||||
|
this.actionName = "REJECT";
|
||||||
|
this.entityName = "CLEARINGSETTLEMENTBATCH";
|
||||||
|
this.entityId = settlementBatchId;
|
||||||
|
this.href = "/clearing/settlement-batches/" + settlementBatchId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createClearingReconciliation() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CLEARINGRECONCILIATION";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/clearing/reconciliations";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createInwardClearingBatch() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "INWARDCLEARINGBATCH";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/ctsclearing/inward-batches";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder validateInwardClearingBatch(final Long batchId) {
|
||||||
|
this.actionName = "VALIDATE";
|
||||||
|
this.entityName = "INWARDCLEARINGBATCH";
|
||||||
|
this.entityId = batchId;
|
||||||
|
this.href = "/ctsclearing/inward-batches/" + batchId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder passInwardClearingItem(final Long itemId) {
|
||||||
|
this.actionName = "PASS";
|
||||||
|
this.entityName = "INWARDCLEARINGITEM";
|
||||||
|
this.entityId = itemId;
|
||||||
|
this.href = "/ctsclearing/inward-items/" + itemId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder returnInwardClearingItem(final Long itemId) {
|
||||||
|
this.actionName = "RETURN";
|
||||||
|
this.entityName = "INWARDCLEARINGITEM";
|
||||||
|
this.entityId = itemId;
|
||||||
|
this.href = "/ctsclearing/inward-items/" + itemId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder verifyInwardClearingItemSignature(final Long itemId) {
|
||||||
|
this.actionName = "VERIFY_SIGNATURE";
|
||||||
|
this.entityName = "INWARDCLEARINGITEM";
|
||||||
|
this.entityId = itemId;
|
||||||
|
this.href = "/ctsclearing/inward-items/" + itemId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createPositivePayRegistration() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "POSITIVEPAYREGISTRATION";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/ctsclearing/positive-pay";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder cancelPositivePayRegistration(final Long registrationId) {
|
||||||
|
this.actionName = "CANCEL";
|
||||||
|
this.entityName = "POSITIVEPAYREGISTRATION";
|
||||||
|
this.entityId = registrationId;
|
||||||
|
this.href = "/ctsclearing/positive-pay/" + registrationId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder sendPendingOutwardClearingBatch() {
|
||||||
|
this.actionName = "SENDBATCH";
|
||||||
|
this.entityName = "OUTWARDCLEARINGPRESENTMENT";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/ctsclearing/outward-presentments/send-pending-batch";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createOutwardClearingPresentment() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "OUTWARDCLEARINGPRESENTMENT";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/ctsclearing/outward-presentments";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder sendOutwardClearingPresentment(final Long presentmentId) {
|
||||||
|
this.actionName = "SEND";
|
||||||
|
this.entityName = "OUTWARDCLEARINGPRESENTMENT";
|
||||||
|
this.entityId = presentmentId;
|
||||||
|
this.href = "/ctsclearing/outward-presentments/" + presentmentId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder settleOutwardClearingPresentment(final Long presentmentId) {
|
||||||
|
this.actionName = "SETTLE";
|
||||||
|
this.entityName = "OUTWARDCLEARINGPRESENTMENT";
|
||||||
|
this.entityId = presentmentId;
|
||||||
|
this.href = "/ctsclearing/outward-presentments/" + presentmentId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder dishonourOutwardClearingPresentment(final Long presentmentId) {
|
||||||
|
this.actionName = "DISHONOUR";
|
||||||
|
this.entityName = "OUTWARDCLEARINGPRESENTMENT";
|
||||||
|
this.entityId = presentmentId;
|
||||||
|
this.href = "/ctsclearing/outward-presentments/" + presentmentId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createNachMandate() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "NACHMANDATE";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/nachclearing/mandates";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder cancelNachMandate(final Long mandateId) {
|
||||||
|
this.actionName = "CANCEL";
|
||||||
|
this.entityName = "NACHMANDATE";
|
||||||
|
this.entityId = mandateId;
|
||||||
|
this.href = "/nachclearing/mandates/" + mandateId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createNachBatch() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "NACHBATCH";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/nachclearing/batches";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder processNachBatch(final Long batchId) {
|
||||||
|
this.actionName = "PROCESS";
|
||||||
|
this.entityName = "NACHBATCH";
|
||||||
|
this.entityId = batchId;
|
||||||
|
this.href = "/nachclearing/batches/" + batchId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder passNachTransaction(final Long transactionId) {
|
||||||
|
this.actionName = "PASS";
|
||||||
|
this.entityName = "NACHTRANSACTION";
|
||||||
|
this.entityId = transactionId;
|
||||||
|
this.href = "/nachclearing/transactions/" + transactionId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder returnNachTransaction(final Long transactionId) {
|
||||||
|
this.actionName = "RETURN";
|
||||||
|
this.entityName = "NACHTRANSACTION";
|
||||||
|
this.entityId = transactionId;
|
||||||
|
this.href = "/nachclearing/transactions/" + transactionId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder updateCreditBureau() {
|
public CommandWrapperBuilder updateCreditBureau() {
|
||||||
this.actionName = "UPDATE";
|
this.actionName = "UPDATE";
|
||||||
this.entityName = "ORGANISATIONCREDITBUREAU";
|
this.entityName = "ORGANISATIONCREDITBUREAU";
|
||||||
@@ -302,6 +640,14 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createUserAgreement() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "USERAGREEMENT";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/useragreements";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder createOffice() {
|
public CommandWrapperBuilder createOffice() {
|
||||||
this.actionName = "CREATE";
|
this.actionName = "CREATE";
|
||||||
this.entityName = "OFFICE";
|
this.entityName = "OFFICE";
|
||||||
@@ -1611,6 +1957,14 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder terminateAccount(final Long accountId) {
|
||||||
|
this.actionName = "TERMINATE";
|
||||||
|
this.entityName = "ACCOUNT";
|
||||||
|
this.entityId = accountId;
|
||||||
|
this.href = "/accounts/" + accountId + "/terminate";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder approveGSIMAccountApplication(final Long accountId) {
|
public CommandWrapperBuilder approveGSIMAccountApplication(final Long accountId) {
|
||||||
this.actionName = "APPROVE";
|
this.actionName = "APPROVE";
|
||||||
this.entityName = "GSIMACCOUNT";
|
this.entityName = "GSIMACCOUNT";
|
||||||
@@ -3565,7 +3919,7 @@ public class CommandWrapperBuilder {
|
|||||||
this.actionName = "ACTIVATE";
|
this.actionName = "ACTIVATE";
|
||||||
this.entityName = "CURRENTACCOUNT";
|
this.entityName = "CURRENTACCOUNT";
|
||||||
this.savingsId = accountId;
|
this.savingsId = accountId;
|
||||||
this.entityId = null;
|
this.entityId = accountId;
|
||||||
this.href = "/currentaccounts/" + accountId + "?command=activate";
|
this.href = "/currentaccounts/" + accountId + "?command=activate";
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -20,6 +20,7 @@ package org.apache.fineract.commands.service;
|
|||||||
|
|
||||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR;
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR;
|
||||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.PROCESSED;
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.PROCESSED;
|
||||||
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.TERMINATE;
|
||||||
import static org.apache.http.HttpStatus.SC_OK;
|
import static org.apache.http.HttpStatus.SC_OK;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
@@ -181,7 +182,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
currentSource.setResultStatusCode(SC_OK);
|
currentSource.setResultStatusCode(SC_OK);
|
||||||
currentSource.updateForAudit(result);
|
currentSource.updateForAudit(result);
|
||||||
currentSource.setResult(toApiResultJsonSerializer.serializeResult(result));
|
currentSource.setResult(toApiResultJsonSerializer.serializeResult(result));
|
||||||
currentSource.setStatus(PROCESSED);
|
currentSource.setStatus("TERMINATE".equals(wrapper.getActionName()) ? TERMINATE : PROCESSED);
|
||||||
|
|
||||||
// Return saved command source
|
// Return saved command source
|
||||||
return commandSourceService.saveResultSameTransaction(currentSource);
|
return commandSourceService.saveResultSameTransaction(currentSource);
|
||||||
|
|||||||
+150
-116
@@ -1,116 +1,150 @@
|
|||||||
/**
|
/**
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
* or more contributor license agreements. See the NOTICE file
|
* or more contributor license agreements. See the NOTICE file
|
||||||
* distributed with this work for additional information
|
* distributed with this work for additional information
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
* to you under the Apache License, Version 2.0 (the
|
* to you under the Apache License, Version 2.0 (the
|
||||||
* "License"); you may not use this file except in compliance
|
* "License"); you may not use this file except in compliance
|
||||||
* with the License. You may obtain a copy of the License at
|
* with the License. You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing,
|
* Unless required by applicable law or agreed to in writing,
|
||||||
* software distributed under the License is distributed on an
|
* software distributed under the License is distributed on an
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
* KIND, either express or implied. See the License for the
|
* KIND, either express or implied. See the License for the
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.core.serialization;
|
package org.apache.fineract.infrastructure.core.serialization;
|
||||||
|
|
||||||
import com.google.gson.ExclusionStrategy;
|
import com.google.gson.ExclusionStrategy;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import java.time.LocalDate;
|
import com.google.gson.TypeAdapter;
|
||||||
import java.time.LocalDateTime;
|
import com.google.gson.TypeAdapterFactory;
|
||||||
import java.time.LocalTime;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import java.time.MonthDay;
|
import com.google.gson.stream.JsonReader;
|
||||||
import java.time.OffsetDateTime;
|
import com.google.gson.stream.JsonWriter;
|
||||||
import java.time.ZonedDateTime;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.time.LocalDate;
|
||||||
import java.util.HashSet;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Set;
|
import java.time.LocalTime;
|
||||||
import org.apache.fineract.infrastructure.core.api.DateAdapter;
|
import java.time.MonthDay;
|
||||||
import org.apache.fineract.infrastructure.core.api.ExternalIdAdapter;
|
import java.time.OffsetDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
|
import java.time.ZonedDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
|
import java.util.ArrayList;
|
||||||
import org.apache.fineract.infrastructure.core.api.LocalDateAdapter;
|
import java.util.HashSet;
|
||||||
import org.apache.fineract.infrastructure.core.api.LocalDateTimeAdapter;
|
import java.util.Set;
|
||||||
import org.apache.fineract.infrastructure.core.api.LocalTimeAdapter;
|
import org.apache.fineract.infrastructure.core.api.DateAdapter;
|
||||||
import org.apache.fineract.infrastructure.core.api.OffsetDateTimeAdapter;
|
import org.apache.fineract.infrastructure.core.api.ExternalIdAdapter;
|
||||||
import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy;
|
import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
|
||||||
import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy;
|
import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.api.LocalDateAdapter;
|
||||||
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
|
import org.apache.fineract.infrastructure.core.api.LocalDateTimeAdapter;
|
||||||
import org.springframework.stereotype.Service;
|
import org.apache.fineract.infrastructure.core.api.LocalTimeAdapter;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.OffsetDateTimeAdapter;
|
||||||
/**
|
import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy;
|
||||||
* Helper class for serialization of Java objects into JSON using Google's GSON.
|
import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy;
|
||||||
*/
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
@Service
|
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
|
||||||
public final class GoogleGsonSerializerHelper {
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
public Gson createGsonBuilderForPartialResponseFiltering(final Set<String> responseParameters) {
|
/**
|
||||||
final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters);
|
* Helper class for serialization of Java objects into JSON using Google's GSON.
|
||||||
|
*/
|
||||||
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
|
@Service
|
||||||
registerTypeAdapters(builder);
|
public final class GoogleGsonSerializerHelper {
|
||||||
return builder.create();
|
|
||||||
}
|
public Gson createGsonBuilderForPartialResponseFiltering(final Set<String> responseParameters) {
|
||||||
|
final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters);
|
||||||
public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set<String> supportedParameters,
|
|
||||||
final Set<String> responseParameters) {
|
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
|
||||||
|
registerTypeAdapters(builder);
|
||||||
final Set<String> parameterNamesToSkip = new HashSet<>();
|
return builder.create();
|
||||||
|
}
|
||||||
if (!responseParameters.isEmpty()) {
|
|
||||||
// strip out all known support parameters from expected response to
|
public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set<String> supportedParameters,
|
||||||
// see if unsupported parameters requested for response.
|
final Set<String> responseParameters) {
|
||||||
final Set<String> differentParametersDetectedSet = new HashSet<>(responseParameters);
|
|
||||||
differentParametersDetectedSet.removeAll(supportedParameters);
|
final Set<String> parameterNamesToSkip = new HashSet<>();
|
||||||
|
|
||||||
if (!differentParametersDetectedSet.isEmpty()) {
|
if (!responseParameters.isEmpty()) {
|
||||||
throw new UnsupportedParameterException(new ArrayList<>(differentParametersDetectedSet));
|
// strip out all known support parameters from expected response to
|
||||||
}
|
// see if unsupported parameters requested for response.
|
||||||
|
final Set<String> differentParametersDetectedSet = new HashSet<>(responseParameters);
|
||||||
parameterNamesToSkip.addAll(supportedParameters);
|
differentParametersDetectedSet.removeAll(supportedParameters);
|
||||||
parameterNamesToSkip.removeAll(responseParameters);
|
|
||||||
}
|
if (!differentParametersDetectedSet.isEmpty()) {
|
||||||
|
throw new UnsupportedParameterException(new ArrayList<>(differentParametersDetectedSet));
|
||||||
final ExclusionStrategy strategy = new ParameterListExclusionStrategy(parameterNamesToSkip);
|
}
|
||||||
|
|
||||||
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
|
parameterNamesToSkip.addAll(supportedParameters);
|
||||||
registerTypeAdapters(builder);
|
parameterNamesToSkip.removeAll(responseParameters);
|
||||||
return builder.create();
|
}
|
||||||
}
|
|
||||||
|
final ExclusionStrategy strategy = new ParameterListExclusionStrategy(parameterNamesToSkip);
|
||||||
public String serializedJsonFrom(final Gson serializer, final Object[] dataObjects) {
|
|
||||||
return serializer.toJson(dataObjects);
|
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
|
||||||
}
|
registerTypeAdapters(builder);
|
||||||
|
return builder.create();
|
||||||
public String serializedJsonFrom(final Gson serializer, final Object singleDataObject) {
|
}
|
||||||
return serializer.toJson(singleDataObject);
|
|
||||||
}
|
public String serializedJsonFrom(final Gson serializer, final Object[] dataObjects) {
|
||||||
|
return serializer.toJson(dataObjects);
|
||||||
public static Gson createSimpleGson() {
|
}
|
||||||
return createGsonBuilder().create();
|
|
||||||
}
|
public String serializedJsonFrom(final Gson serializer, final Object singleDataObject) {
|
||||||
|
return serializer.toJson(singleDataObject);
|
||||||
public static GsonBuilder createGsonBuilder() {
|
}
|
||||||
final GsonBuilder builder = new GsonBuilder();
|
|
||||||
registerTypeAdapters(builder);
|
public static Gson createSimpleGson() {
|
||||||
return builder;
|
return createGsonBuilder().create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerTypeAdapters(final GsonBuilder builder) {
|
public static GsonBuilder createGsonBuilder() {
|
||||||
builder.registerTypeAdapter(java.util.Date.class, new DateAdapter());
|
final GsonBuilder builder = new GsonBuilder();
|
||||||
builder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter());
|
registerTypeAdapters(builder);
|
||||||
// NOTE: was missing, necessary for GSON serialization with JDK 21's restrictive module access
|
return builder;
|
||||||
builder.registerTypeAdapter(LocalTime.class, new LocalTimeAdapter());
|
}
|
||||||
builder.registerTypeAdapter(ZonedDateTime.class, new JodaDateTimeAdapter());
|
|
||||||
builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
|
public static void registerTypeAdapters(final GsonBuilder builder) {
|
||||||
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
|
builder.registerTypeAdapter(java.util.Date.class, new DateAdapter());
|
||||||
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
|
builder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter());
|
||||||
builder.registerTypeAdapter(ExternalId.class, new ExternalIdAdapter());
|
// NOTE: was missing, necessary for GSON serialization with JDK 21's restrictive module access
|
||||||
}
|
builder.registerTypeAdapter(LocalTime.class, new LocalTimeAdapter());
|
||||||
}
|
builder.registerTypeAdapter(ZonedDateTime.class, new JodaDateTimeAdapter());
|
||||||
|
builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
|
||||||
|
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
|
||||||
|
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
|
||||||
|
builder.registerTypeAdapter(ExternalId.class, new ExternalIdAdapter());
|
||||||
|
// Use runtime type when serializing interface-typed fields (e.g. Collection<? extends IGuarantor>)
|
||||||
|
builder.registerTypeAdapterFactory(new TypeAdapterFactory() {
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
|
||||||
|
if (!type.getRawType().isInterface()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final TypeAdapterFactory self = this;
|
||||||
|
return new TypeAdapter<T>() {
|
||||||
|
@Override
|
||||||
|
public void write(final JsonWriter out, final T value) throws IOException {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TypeAdapter<T> delegate = (TypeAdapter<T>) gson.getDelegateAdapter(self,
|
||||||
|
TypeToken.get((Class<T>) value.getClass()));
|
||||||
|
delegate.write(out, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T read(final JsonReader in) throws IOException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.infrastructure.documentmanagement.adapter;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decouples the entity "current signature" pointer from the generic document service, the same
|
||||||
|
* way {@link EntityImageIdAdapter} decouples the "current photo" pointer — lets
|
||||||
|
* DocumentWritePlatformServiceImpl (fineract-document module) give single-slot upsert semantics to
|
||||||
|
* signature uploads without depending on portfolio-module entities directly.
|
||||||
|
*/
|
||||||
|
public interface EntitySignatureIdAdapter {
|
||||||
|
|
||||||
|
/** @param documentName the m_document.name of the document being created/removed */
|
||||||
|
boolean accept(String entityType, String documentName);
|
||||||
|
|
||||||
|
Optional<SignatureIdResult> get(Long entityId);
|
||||||
|
|
||||||
|
Optional<SignatureIdResult> set(Long entityId, Long signatureId);
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
class SignatureIdResult implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -61,6 +61,12 @@ public enum JobName {
|
|||||||
ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Savings"), //
|
ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Savings"), //
|
||||||
JOURNAL_ENTRY_AGGREGATION("Journal Entry Aggregation"), //
|
JOURNAL_ENTRY_AGGREGATION("Journal Entry Aggregation"), //
|
||||||
FETCH_RBI_RSS_FEEDS("Fetch RBI RSS Feeds"), //
|
FETCH_RBI_RSS_FEEDS("Fetch RBI RSS Feeds"), //
|
||||||
|
IMPS_DEEMED_RECONCILE("IMPS DEEMED Reconciliation"), //
|
||||||
|
NEFT_BATCH_PROCESS("NEFT Batch Process"), //
|
||||||
|
VERIFICATION_TOKEN_CLEANUP("Verification Token Cleanup"), //
|
||||||
|
OUTWARD_CLEARING_BATCH_SEND("CTS Outward Clearing Batch Send"), //
|
||||||
|
CTR_REPORT_GENERATION("AML Monthly CTR Report Generation"), //
|
||||||
|
AML_STR_WEEKLY_DIGEST("AML STR Weekly Digest"), //
|
||||||
; //
|
; //
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|||||||
+79
-71
@@ -1,71 +1,79 @@
|
|||||||
/**
|
/**
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
* or more contributor license agreements. See the NOTICE file
|
* or more contributor license agreements. See the NOTICE file
|
||||||
* distributed with this work for additional information
|
* distributed with this work for additional information
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
* to you under the Apache License, Version 2.0 (the
|
* to you under the Apache License, Version 2.0 (the
|
||||||
* "License"); you may not use this file except in compliance
|
* "License"); you may not use this file except in compliance
|
||||||
* with the License. You may obtain a copy of the License at
|
* with the License. You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing,
|
* Unless required by applicable law or agreed to in writing,
|
||||||
* software distributed under the License is distributed on an
|
* software distributed under the License is distributed on an
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
* KIND, either express or implied. See the License for the
|
* KIND, either express or implied. See the License for the
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.account;
|
package org.apache.fineract.portfolio.account;
|
||||||
|
|
||||||
public enum PortfolioAccountType {
|
public enum PortfolioAccountType {
|
||||||
|
|
||||||
INVALID(0, "accountType.invalid"), //
|
INVALID(0, "accountType.invalid"), //
|
||||||
LOAN(1, "accountType.loan"), //
|
LOAN(1, "accountType.loan"), //
|
||||||
SAVINGS(2, "accountType.savings"), //
|
SAVINGS(2, "accountType.savings"), //
|
||||||
;
|
CURRENT(3, "accountType.current"), //
|
||||||
|
;
|
||||||
private final Integer value;
|
|
||||||
private final String code;
|
private final Integer value;
|
||||||
|
private final String code;
|
||||||
PortfolioAccountType(final Integer value, final String code) {
|
|
||||||
this.value = value;
|
PortfolioAccountType(final Integer value, final String code) {
|
||||||
this.code = code;
|
this.value = value;
|
||||||
}
|
this.code = code;
|
||||||
|
}
|
||||||
public Integer getValue() {
|
|
||||||
return this.value;
|
public Integer getValue() {
|
||||||
}
|
return this.value;
|
||||||
|
}
|
||||||
public String getCode() {
|
|
||||||
return this.code;
|
public String getCode() {
|
||||||
}
|
return this.code;
|
||||||
|
}
|
||||||
public static PortfolioAccountType fromInt(final Integer type) {
|
|
||||||
|
public static PortfolioAccountType fromInt(final Integer type) {
|
||||||
PortfolioAccountType enumType = INVALID;
|
|
||||||
if (type != null) {
|
PortfolioAccountType enumType = INVALID;
|
||||||
switch (type) {
|
if (type != null) {
|
||||||
case 1:
|
switch (type) {
|
||||||
enumType = LOAN;
|
case 1:
|
||||||
break;
|
enumType = LOAN;
|
||||||
case 2:
|
break;
|
||||||
enumType = SAVINGS;
|
case 2:
|
||||||
break;
|
enumType = SAVINGS;
|
||||||
default:
|
break;
|
||||||
enumType = INVALID;
|
case 3:
|
||||||
}
|
enumType = CURRENT;
|
||||||
}
|
break;
|
||||||
return enumType;
|
default:
|
||||||
}
|
enumType = INVALID;
|
||||||
|
}
|
||||||
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
}
|
||||||
public boolean isSavingsAccount() {
|
return enumType;
|
||||||
return this.equals(SAVINGS);
|
}
|
||||||
}
|
|
||||||
|
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
||||||
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
public boolean isSavingsAccount() {
|
||||||
public boolean isLoanAccount() {
|
return this.equals(SAVINGS);
|
||||||
return this.equals(LOAN);
|
}
|
||||||
}
|
|
||||||
}
|
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
||||||
|
public boolean isLoanAccount() {
|
||||||
|
return this.equals(LOAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCurrentAccount() {
|
||||||
|
return this.equals(CURRENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One row per {@link AmlTransactionRule} hit on a live transaction. Raised regardless of the
|
||||||
|
* client's current KYC risk category - alerting is never suppressed by a client being Low/Medium
|
||||||
|
* risk. First reviewed by an AML Officer (whitelist or escalate); an ESCALATED alert spawns an
|
||||||
|
* {@link AmlStrCase} for the Principal Officer.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_aml_alert")
|
||||||
|
public class AmlAlert extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||||
|
|
||||||
|
@Column(name = "transaction_type", nullable = false, length = 20)
|
||||||
|
private String transactionType;
|
||||||
|
|
||||||
|
@Column(name = "transaction_id", nullable = false)
|
||||||
|
private Long transactionId;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "client_id", nullable = false)
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "rule_id", nullable = false)
|
||||||
|
private AmlTransactionRule rule;
|
||||||
|
|
||||||
|
@Column(name = "amount", nullable = false, precision = 19, scale = 6)
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
@Column(name = "currency_code", length = 3)
|
||||||
|
private String currencyCode;
|
||||||
|
|
||||||
|
@Column(name = "raised_on", nullable = false)
|
||||||
|
private LocalDateTime raisedOn;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = 20)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "reviewed_by")
|
||||||
|
private AppUser reviewedBy;
|
||||||
|
|
||||||
|
@Column(name = "reviewed_on")
|
||||||
|
private LocalDateTime reviewedOn;
|
||||||
|
|
||||||
|
@Column(name = "review_notes", length = 1000)
|
||||||
|
private String reviewNotes;
|
||||||
|
|
||||||
|
@Column(name = "disposition", length = 20)
|
||||||
|
private String disposition;
|
||||||
|
|
||||||
|
protected AmlAlert() {}
|
||||||
|
|
||||||
|
public static AmlAlert raise(AmlTransactionType transactionType, Long transactionId, Client client, AmlTransactionRule rule,
|
||||||
|
BigDecimal amount, String currencyCode, LocalDateTime raisedOn) {
|
||||||
|
final AmlAlert alert = new AmlAlert();
|
||||||
|
alert.transactionType = transactionType.name();
|
||||||
|
alert.transactionId = transactionId;
|
||||||
|
alert.client = client;
|
||||||
|
alert.rule = rule;
|
||||||
|
alert.amount = amount;
|
||||||
|
alert.currencyCode = currencyCode;
|
||||||
|
alert.raisedOn = raisedOn;
|
||||||
|
alert.status = AmlAlertStatus.RAISED.name();
|
||||||
|
return alert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void whitelist(AppUser reviewer, String notes) {
|
||||||
|
this.reviewedBy = reviewer;
|
||||||
|
this.reviewedOn = LocalDateTime.now();
|
||||||
|
this.reviewNotes = notes;
|
||||||
|
this.disposition = AmlAlertDisposition.WHITELIST.name();
|
||||||
|
this.status = AmlAlertStatus.WHITELISTED.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void escalate(AppUser reviewer, String notes) {
|
||||||
|
this.reviewedBy = reviewer;
|
||||||
|
this.reviewedOn = LocalDateTime.now();
|
||||||
|
this.reviewNotes = notes;
|
||||||
|
this.disposition = AmlAlertDisposition.ESCALATE.name();
|
||||||
|
this.status = AmlAlertStatus.ESCALATED.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTransactionId() {
|
||||||
|
return transactionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlTransactionType getTransactionType() {
|
||||||
|
return AmlTransactionType.valueOf(transactionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlTransactionRule getRule() {
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrencyCode() {
|
||||||
|
return currencyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getRaisedOn() {
|
||||||
|
return raisedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlAlertStatus getStatus() {
|
||||||
|
return AmlAlertStatus.valueOf(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppUser getReviewedBy() {
|
||||||
|
return reviewedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getReviewedOn() {
|
||||||
|
return reviewedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReviewNotes() {
|
||||||
|
return reviewNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlAlertDisposition getDisposition() {
|
||||||
|
return disposition == null ? null : AmlAlertDisposition.valueOf(disposition);
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
/** The AML Officer's first-level disposition of an {@link AmlAlert}. */
|
||||||
|
public enum AmlAlertDisposition {
|
||||||
|
|
||||||
|
WHITELIST, ESCALATE
|
||||||
|
}
|
||||||
+38
@@ -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.aml.domain;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
public interface AmlAlertRepository extends JpaRepository<AmlAlert, Long> {
|
||||||
|
|
||||||
|
List<AmlAlert> findByStatusOrderByRaisedOnAsc(String status);
|
||||||
|
|
||||||
|
@Query("SELECT COALESCE(SUM(a.amount), 0) FROM AmlAlert a WHERE a.client.id = :clientId "
|
||||||
|
+ "AND a.transactionType = :transactionType AND a.raisedOn >= :since")
|
||||||
|
BigDecimal sumAmountForClientSince(@Param("clientId") Long clientId, @Param("transactionType") String transactionType,
|
||||||
|
@Param("since") LocalDateTime since);
|
||||||
|
|
||||||
|
long countByClientId(Long clientId);
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
public enum AmlAlertStatus {
|
||||||
|
|
||||||
|
RAISED, UNDER_REVIEW, WHITELISTED, ESCALATED
|
||||||
|
}
|
||||||
+29
@@ -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.aml.domain;
|
||||||
|
|
||||||
|
/** Which transaction type(s) an {@link AmlTransactionRule} evaluates against. */
|
||||||
|
public enum AmlAppliesTo {
|
||||||
|
|
||||||
|
SAVINGS, LOAN, TRANSFER, ALL;
|
||||||
|
|
||||||
|
public boolean matches(AmlTransactionType transactionType) {
|
||||||
|
return this == ALL || this.name().equals(transactionType.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monthly, mechanical CTR batch header - purely threshold-based (₹10L+ cash, individually or
|
||||||
|
* connected), no Principal Officer judgment involved. One row per calendar month.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_aml_ctr_report")
|
||||||
|
public class AmlCtrReport extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||||
|
|
||||||
|
@Column(name = "report_month", nullable = false, length = 7)
|
||||||
|
private String reportMonth;
|
||||||
|
|
||||||
|
@Column(name = "generated_on", nullable = false)
|
||||||
|
private LocalDateTime generatedOn;
|
||||||
|
|
||||||
|
@Column(name = "record_count", nullable = false)
|
||||||
|
private int recordCount;
|
||||||
|
|
||||||
|
@Column(name = "file_reference", length = 150)
|
||||||
|
private String fileReference;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = 20)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "submitted_on")
|
||||||
|
private LocalDateTime submittedOn;
|
||||||
|
|
||||||
|
protected AmlCtrReport() {}
|
||||||
|
|
||||||
|
public static AmlCtrReport draft(String reportMonth, LocalDateTime generatedOn, int recordCount) {
|
||||||
|
final AmlCtrReport report = new AmlCtrReport();
|
||||||
|
report.reportMonth = reportMonth;
|
||||||
|
report.generatedOn = generatedOn;
|
||||||
|
report.recordCount = recordCount;
|
||||||
|
report.status = AmlCtrReportStatus.DRAFT.name();
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markGenerated(String fileReference) {
|
||||||
|
this.fileReference = fileReference;
|
||||||
|
this.status = AmlCtrReportStatus.GENERATED.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markSubmitted(LocalDateTime submittedOn) {
|
||||||
|
this.submittedOn = submittedOn;
|
||||||
|
this.status = AmlCtrReportStatus.SUBMITTED.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReportMonth() {
|
||||||
|
return reportMonth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getGeneratedOn() {
|
||||||
|
return generatedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRecordCount() {
|
||||||
|
return recordCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileReference() {
|
||||||
|
return fileReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlCtrReportStatus getStatus() {
|
||||||
|
return AmlCtrReportStatus.valueOf(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getSubmittedOn() {
|
||||||
|
return submittedOn;
|
||||||
|
}
|
||||||
|
}
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One line per transaction (or connected-transaction group, when {@link #connectedGroup} is true)
|
||||||
|
* feeding a monthly {@link AmlCtrReport}.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_aml_ctr_report_line")
|
||||||
|
public class AmlCtrReportLine {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "ctr_report_id", nullable = false)
|
||||||
|
private AmlCtrReport ctrReport;
|
||||||
|
|
||||||
|
@Column(name = "transaction_type", nullable = false, length = 20)
|
||||||
|
private String transactionType;
|
||||||
|
|
||||||
|
@Column(name = "transaction_id")
|
||||||
|
private Long transactionId;
|
||||||
|
|
||||||
|
@Column(name = "client_id", nullable = false)
|
||||||
|
private Long clientId;
|
||||||
|
|
||||||
|
@Column(name = "amount", nullable = false, precision = 19, scale = 6)
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
@Column(name = "is_connected_group", nullable = false)
|
||||||
|
private boolean connectedGroup;
|
||||||
|
|
||||||
|
protected AmlCtrReportLine() {}
|
||||||
|
|
||||||
|
public static AmlCtrReportLine of(AmlCtrReport ctrReport, AmlTransactionType transactionType, Long transactionId, Long clientId,
|
||||||
|
BigDecimal amount, boolean connectedGroup) {
|
||||||
|
final AmlCtrReportLine line = new AmlCtrReportLine();
|
||||||
|
line.ctrReport = ctrReport;
|
||||||
|
line.transactionType = transactionType.name();
|
||||||
|
line.transactionId = transactionId;
|
||||||
|
line.clientId = clientId;
|
||||||
|
line.amount = amount;
|
||||||
|
line.connectedGroup = connectedGroup;
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlCtrReport getCtrReport() {
|
||||||
|
return ctrReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlTransactionType getTransactionType() {
|
||||||
|
return AmlTransactionType.valueOf(transactionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTransactionId() {
|
||||||
|
return transactionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnectedGroup() {
|
||||||
|
return connectedGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.aml.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface AmlCtrReportLineRepository extends JpaRepository<AmlCtrReportLine, Long> {
|
||||||
|
|
||||||
|
List<AmlCtrReportLine> findByCtrReport_Id(Long ctrReportId);
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.aml.domain;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface AmlCtrReportRepository extends JpaRepository<AmlCtrReport, Long> {
|
||||||
|
|
||||||
|
Optional<AmlCtrReport> findByReportMonth(String reportMonth);
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
public enum AmlCtrReportStatus {
|
||||||
|
|
||||||
|
DRAFT, GENERATED, SUBMITTED
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How an {@link AmlTransactionRule} is evaluated.
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>THRESHOLD - a single transaction at or above {@code thresholdAmount}</li>
|
||||||
|
* <li>STRUCTURING - connected transactions summing at/above {@code thresholdAmount} within {@code windowDays}</li>
|
||||||
|
* <li>VELOCITY - unusual frequency of transactions within {@code windowDays}, independent of a fixed amount</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public enum AmlRuleType {
|
||||||
|
|
||||||
|
THRESHOLD, STRUCTURING, VELOCITY
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created when an {@link AmlAlert} is escalated past first-level review. {@link #concludedOn} is
|
||||||
|
* the Principal Officer's conclusion date - the RBI-mandated 7-working-day FIU filing clock
|
||||||
|
* ({@link #fiuDueDate}) starts here, never at the alert or transaction date.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_aml_str_case")
|
||||||
|
public class AmlStrCase extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "alert_id", nullable = false)
|
||||||
|
private AmlAlert alert;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "client_id", nullable = false)
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "assigned_po")
|
||||||
|
private AppUser assignedPo;
|
||||||
|
|
||||||
|
@Column(name = "investigation_notes", length = 2000)
|
||||||
|
private String investigationNotes;
|
||||||
|
|
||||||
|
@Column(name = "source_of_funds_notes", length = 2000)
|
||||||
|
private String sourceOfFundsNotes;
|
||||||
|
|
||||||
|
@Column(name = "conclusion", length = 20)
|
||||||
|
private String conclusion;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "concluded_by")
|
||||||
|
private AppUser concludedBy;
|
||||||
|
|
||||||
|
@Column(name = "concluded_on")
|
||||||
|
private LocalDate concludedOn;
|
||||||
|
|
||||||
|
@Column(name = "fiu_due_date")
|
||||||
|
private LocalDate fiuDueDate;
|
||||||
|
|
||||||
|
@Column(name = "str_filed_on")
|
||||||
|
private LocalDate strFiledOn;
|
||||||
|
|
||||||
|
@Column(name = "str_reference_no", length = 100)
|
||||||
|
private String strReferenceNo;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = 25)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
protected AmlStrCase() {}
|
||||||
|
|
||||||
|
public static AmlStrCase openFor(AmlAlert alert) {
|
||||||
|
final AmlStrCase strCase = new AmlStrCase();
|
||||||
|
strCase.alert = alert;
|
||||||
|
strCase.client = alert.getClient();
|
||||||
|
strCase.status = AmlStrCaseStatus.OPEN.name();
|
||||||
|
return strCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assignPrincipalOfficer(AppUser po) {
|
||||||
|
this.assignedPo = po;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@code fiuDueDate} is computed by the caller using the working-days calendar, not here. */
|
||||||
|
public void conclude(AppUser po, AmlStrConclusion conclusion, String investigationNotes, String sourceOfFundsNotes,
|
||||||
|
LocalDate concludedOn, LocalDate fiuDueDate) {
|
||||||
|
this.concludedBy = po;
|
||||||
|
this.conclusion = conclusion.name();
|
||||||
|
this.investigationNotes = investigationNotes;
|
||||||
|
this.sourceOfFundsNotes = sourceOfFundsNotes;
|
||||||
|
this.concludedOn = concludedOn;
|
||||||
|
this.status = conclusion == AmlStrConclusion.SUSPICIOUS ? AmlStrCaseStatus.CONCLUDED_SUSPICIOUS.name()
|
||||||
|
: AmlStrCaseStatus.CONCLUDED_CLEAR.name();
|
||||||
|
this.fiuDueDate = conclusion == AmlStrConclusion.SUSPICIOUS ? fiuDueDate : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fileStr(String referenceNo, LocalDate filedOn) {
|
||||||
|
this.strReferenceNo = referenceNo;
|
||||||
|
this.strFiledOn = filedOn;
|
||||||
|
this.status = AmlStrCaseStatus.FILED.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getClientId() {
|
||||||
|
return client.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlAlert getAlert() {
|
||||||
|
return alert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppUser getAssignedPo() {
|
||||||
|
return assignedPo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInvestigationNotes() {
|
||||||
|
return investigationNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceOfFundsNotes() {
|
||||||
|
return sourceOfFundsNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlStrConclusion getConclusion() {
|
||||||
|
return conclusion == null ? null : AmlStrConclusion.valueOf(conclusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppUser getConcludedBy() {
|
||||||
|
return concludedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getConcludedOn() {
|
||||||
|
return concludedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getFiuDueDate() {
|
||||||
|
return fiuDueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getStrFiledOn() {
|
||||||
|
return strFiledOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStrReferenceNo() {
|
||||||
|
return strReferenceNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlStrCaseStatus getStatus() {
|
||||||
|
return AmlStrCaseStatus.valueOf(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -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.aml.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
public interface AmlStrCaseRepository extends JpaRepository<AmlStrCase, Long> {
|
||||||
|
|
||||||
|
long countByClient_Id(Long clientId);
|
||||||
|
|
||||||
|
List<AmlStrCase> findByStatus(String status);
|
||||||
|
|
||||||
|
@Query("SELECT c FROM AmlStrCase c WHERE c.status = 'CONCLUDED_SUSPICIOUS' AND c.concludedOn BETWEEN :from AND :to")
|
||||||
|
List<AmlStrCase> findConcludedSuspiciousBetween(@Param("from") LocalDate from, @Param("to") LocalDate to);
|
||||||
|
|
||||||
|
@Query("SELECT c FROM AmlStrCase c WHERE c.status = 'CONCLUDED_SUSPICIOUS' AND c.fiuDueDate < :asOf")
|
||||||
|
List<AmlStrCase> findOverdueForFiling(@Param("asOf") LocalDate asOf);
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
public enum AmlStrCaseStatus {
|
||||||
|
|
||||||
|
OPEN, CONCLUDED_CLEAR, CONCLUDED_SUSPICIOUS, FILED
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
/** The Principal Officer's final conclusion on an {@link AmlStrCase}. */
|
||||||
|
public enum AmlStrConclusion {
|
||||||
|
|
||||||
|
NOT_SUSPICIOUS, SUSPICIOUS
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A machine-evaluable transaction-monitoring rule. Distinct from {@code ClientRiskRule}
|
||||||
|
* (client-profile, KYC-time scoring) - these are checked against live savings/loan/transfer
|
||||||
|
* transactions as they post. {@link #sourceClientRiskRuleId} optionally traces a rule back to its
|
||||||
|
* originating entry in the 221-rule AML catalog seeded in {@code m_client_risk_rule}.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_aml_transaction_rule")
|
||||||
|
public class AmlTransactionRule {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "rule_code", nullable = false, length = 50)
|
||||||
|
private String ruleCode;
|
||||||
|
|
||||||
|
@Column(name = "description", nullable = false, length = 500)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Column(name = "applies_to", nullable = false, length = 20)
|
||||||
|
private String appliesTo;
|
||||||
|
|
||||||
|
@Column(name = "threshold_amount", precision = 19, scale = 6)
|
||||||
|
private BigDecimal thresholdAmount;
|
||||||
|
|
||||||
|
@Column(name = "rule_type", nullable = false, length = 20)
|
||||||
|
private String ruleType;
|
||||||
|
|
||||||
|
@Column(name = "window_days")
|
||||||
|
private Integer windowDays;
|
||||||
|
|
||||||
|
@Column(name = "source_client_risk_rule_id")
|
||||||
|
private Long sourceClientRiskRuleId;
|
||||||
|
|
||||||
|
@Column(name = "is_active", nullable = false)
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
protected AmlTransactionRule() {}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRuleCode() {
|
||||||
|
return ruleCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlAppliesTo getAppliesTo() {
|
||||||
|
return AmlAppliesTo.valueOf(appliesTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getThresholdAmount() {
|
||||||
|
return thresholdAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlRuleType getRuleType() {
|
||||||
|
return AmlRuleType.valueOf(ruleType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getWindowDays() {
|
||||||
|
return windowDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSourceClientRiskRuleId() {
|
||||||
|
return sourceClientRiskRuleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.aml.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface AmlTransactionRuleRepository extends JpaRepository<AmlTransactionRule, Long> {
|
||||||
|
|
||||||
|
List<AmlTransactionRule> findByActiveTrueAndAppliesTo(String appliesTo);
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
/** The kind of ledger transaction an {@link AmlAlert} was raised against. */
|
||||||
|
public enum AmlTransactionType {
|
||||||
|
|
||||||
|
SAVINGS, LOAN, TRANSFER
|
||||||
|
}
|
||||||
+138
@@ -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.aml.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable, append-only audit trail of a client's KYC risk-category changes. Backs the AML menu's
|
||||||
|
* existing "Risk Update History Report" item, which previously had no data behind it. Written both
|
||||||
|
* for manual re-categorization and for the automatic escalation triggered on a client's 3rd
|
||||||
|
* cumulative {@link AmlStrCase}.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_client_risk_category_history")
|
||||||
|
public class ClientRiskCategoryHistory {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "client_id", nullable = false)
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@Column(name = "previous_category", nullable = false)
|
||||||
|
private Integer previousCategory;
|
||||||
|
|
||||||
|
@Column(name = "new_category", nullable = false)
|
||||||
|
private Integer newCategory;
|
||||||
|
|
||||||
|
@Column(name = "reason", nullable = false, length = 500)
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
@Column(name = "triggered_by", nullable = false, length = 30)
|
||||||
|
private String triggeredBy;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "str_case_id")
|
||||||
|
private AmlStrCase strCase;
|
||||||
|
|
||||||
|
@Column(name = "changed_on", nullable = false)
|
||||||
|
private LocalDateTime changedOn;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "changed_by", nullable = false)
|
||||||
|
private AppUser changedBy;
|
||||||
|
|
||||||
|
protected ClientRiskCategoryHistory() {}
|
||||||
|
|
||||||
|
public static ClientRiskCategoryHistory autoEscalation(Client client, Integer previousCategory, Integer newCategory,
|
||||||
|
AmlStrCase triggeringCase, AppUser systemActor, LocalDateTime changedOn) {
|
||||||
|
final ClientRiskCategoryHistory history = new ClientRiskCategoryHistory();
|
||||||
|
history.client = client;
|
||||||
|
history.previousCategory = previousCategory;
|
||||||
|
history.newCategory = newCategory;
|
||||||
|
history.reason = "Auto-escalated after client's 3rd cumulative AML STR case (case #" + triggeringCase.getId() + ")";
|
||||||
|
history.triggeredBy = RiskCategoryChangeTrigger.AML_AUTO_ESCALATION.name();
|
||||||
|
history.strCase = triggeringCase;
|
||||||
|
history.changedOn = changedOn;
|
||||||
|
history.changedBy = systemActor;
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClientRiskCategoryHistory manual(Client client, Integer previousCategory, Integer newCategory, String reason,
|
||||||
|
AppUser changedBy, LocalDateTime changedOn) {
|
||||||
|
final ClientRiskCategoryHistory history = new ClientRiskCategoryHistory();
|
||||||
|
history.client = client;
|
||||||
|
history.previousCategory = previousCategory;
|
||||||
|
history.newCategory = newCategory;
|
||||||
|
history.reason = reason;
|
||||||
|
history.triggeredBy = RiskCategoryChangeTrigger.MANUAL.name();
|
||||||
|
history.changedBy = changedBy;
|
||||||
|
history.changedOn = changedOn;
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPreviousCategory() {
|
||||||
|
return previousCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNewCategory() {
|
||||||
|
return newCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RiskCategoryChangeTrigger getTriggeredBy() {
|
||||||
|
return RiskCategoryChangeTrigger.valueOf(triggeredBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlStrCase getStrCase() {
|
||||||
|
return strCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getChangedOn() {
|
||||||
|
return changedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppUser getChangedBy() {
|
||||||
|
return changedBy;
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.aml.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface ClientRiskCategoryHistoryRepository extends JpaRepository<ClientRiskCategoryHistory, Long> {
|
||||||
|
|
||||||
|
List<ClientRiskCategoryHistory> findByClient_IdOrderByChangedOnDesc(Long clientId);
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.aml.domain;
|
||||||
|
|
||||||
|
public enum RiskCategoryChangeTrigger {
|
||||||
|
|
||||||
|
AML_AUTO_ESCALATION, MANUAL
|
||||||
|
}
|
||||||
+2
-1
@@ -23,6 +23,7 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulator-side audit log for CFCFRMS (I4C/MHA) events.
|
* Simulator-side audit log for CFCFRMS (I4C/MHA) events.
|
||||||
@@ -69,7 +70,7 @@ public class CfcfrmsSimulatorLog extends AbstractAuditableWithUTCDateTimeCustom<
|
|||||||
this.eventType = eventType;
|
this.eventType = eventType;
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
this.responsePayload = responsePayload;
|
this.responsePayload = responsePayload;
|
||||||
this.eventTimestamp = OffsetDateTime.now();
|
this.eventTimestamp = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CfcfrmsSimulatorLog(final String cfcfrmsRef, final String ticketId, final String bankIfsc,
|
public CfcfrmsSimulatorLog(final String cfcfrmsRef, final String ticketId, final String bankIfsc,
|
||||||
|
|||||||
+2
-1
@@ -23,6 +23,7 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audit trail for every status change on a CFCFRMS complaint ticket.
|
* Audit trail for every status change on a CFCFRMS complaint ticket.
|
||||||
@@ -54,7 +55,7 @@ public class ComplaintAuditLog extends AbstractAuditableWithUTCDateTimeCustom<Lo
|
|||||||
this.event = event;
|
this.event = event;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.actor = actor;
|
this.actor = actor;
|
||||||
this.eventTimestamp = OffsetDateTime.now();
|
this.eventTimestamp = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComplaintAuditLog(final String ticketId, final String event, final String description,
|
public ComplaintAuditLog(final String ticketId, final String event, final String description,
|
||||||
|
|||||||
+3
-2
@@ -26,6 +26,7 @@ import jakarta.persistence.Table;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CFCFRMS Complaint Ticket — represents a cyber fraud complaint pushed by MHA/I4C to the bank.
|
* CFCFRMS Complaint Ticket — represents a cyber fraud complaint pushed by MHA/I4C to the bank.
|
||||||
@@ -121,7 +122,7 @@ public class ComplaintTicket extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
ticket.status = ComplaintStatus.RECEIVED;
|
ticket.status = ComplaintStatus.RECEIVED;
|
||||||
ticket.assignedOfficer = assignedOfficer;
|
ticket.assignedOfficer = assignedOfficer;
|
||||||
ticket.statePoliceRef = statePoliceRef;
|
ticket.statePoliceRef = statePoliceRef;
|
||||||
ticket.receivedAt = OffsetDateTime.now();
|
ticket.receivedAt = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
return ticket;
|
return ticket;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@ public class ComplaintTicket extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
this.status = newStatus;
|
this.status = newStatus;
|
||||||
this.actionTaken = actionNote;
|
this.actionTaken = actionNote;
|
||||||
if (newStatus == ComplaintStatus.RESOLVED || newStatus == ComplaintStatus.REJECTED) {
|
if (newStatus == ComplaintStatus.RESOLVED || newStatus == ComplaintStatus.REJECTED) {
|
||||||
this.resolvedAt = OffsetDateTime.now();
|
this.resolvedAt = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+66
@@ -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.client.adapter;
|
||||||
|
|
||||||
|
import static java.util.Objects.nonNull;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.adapter.EntitySignatureIdAdapter;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientRepository;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
class ClientSignatureIdAdapter implements EntitySignatureIdAdapter {
|
||||||
|
|
||||||
|
private static final String ENTITY_TYPE = "clients";
|
||||||
|
private static final String DOCUMENT_NAME = "clientSignature";
|
||||||
|
|
||||||
|
private final ClientRepository repository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(String entityType, String documentName) {
|
||||||
|
return ENTITY_TYPE.equalsIgnoreCase(entityType) && DOCUMENT_NAME.equalsIgnoreCase(documentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<SignatureIdResult> get(Long entityId) {
|
||||||
|
return repository.findById(entityId).filter(client -> nonNull(client.getSignatureId()))
|
||||||
|
.map(client -> SignatureIdResult.builder().id(client.getSignatureId()).displayName(client.getDisplayName()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Optional<SignatureIdResult> set(Long entityId, Long signatureId) {
|
||||||
|
final var result = get(entityId);
|
||||||
|
|
||||||
|
if (signatureId == null) {
|
||||||
|
repository.removeSignatureId(entityId);
|
||||||
|
} else {
|
||||||
|
repository.updateByIdAndSignatureId(entityId, signatureId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-2
@@ -63,6 +63,7 @@ public class ClientApiConstants {
|
|||||||
public static final String CLIENT_CLASSIFICATION = "ClientClassification";
|
public static final String CLIENT_CLASSIFICATION = "ClientClassification";
|
||||||
|
|
||||||
public static final String OCCUPATION_CATEGORY = "OccupationCategory";
|
public static final String OCCUPATION_CATEGORY = "OccupationCategory";
|
||||||
|
public static final String PWD_CATEGORY = "PwdCategory";
|
||||||
public static final String OCCUPATION = "Occupation";
|
public static final String OCCUPATION = "Occupation";
|
||||||
|
|
||||||
public static final String CLIENT_NON_PERSON_CONSTITUTION = "Constitution";
|
public static final String CLIENT_NON_PERSON_CONSTITUTION = "Constitution";
|
||||||
@@ -96,6 +97,15 @@ public class ClientApiConstants {
|
|||||||
public static final String mobileNoParamName = "mobileNo";
|
public static final String mobileNoParamName = "mobileNo";
|
||||||
public static final String alternateMobileNoParamName = "alternateMobileNo";
|
public static final String alternateMobileNoParamName = "alternateMobileNo";
|
||||||
public static final String emailAddressParamName = "emailAddress";
|
public static final String emailAddressParamName = "emailAddress";
|
||||||
|
// Dual-OTP contact-change verification (see ClientContactOtpService) — required only when
|
||||||
|
// mobileNo or emailAddress is being changed via PUT /clients/{id}. Reference an already
|
||||||
|
// VERIFIED m_otp_log row for the OLD destination (current DB value) and the NEW destination
|
||||||
|
// (incoming value) respectively. Field-specific (not a single generic pair) so mobileNo and
|
||||||
|
// emailAddress can both change in the same request without ambiguity.
|
||||||
|
public static final String mobileOldOtpIdParamName = "mobileOldOtpId";
|
||||||
|
public static final String mobileNewOtpIdParamName = "mobileNewOtpId";
|
||||||
|
public static final String emailOldOtpIdParamName = "emailOldOtpId";
|
||||||
|
public static final String emailNewOtpIdParamName = "emailNewOtpId";
|
||||||
public static final String firstnameParamName = "firstname";
|
public static final String firstnameParamName = "firstname";
|
||||||
public static final String middlenameParamName = "middlename";
|
public static final String middlenameParamName = "middlename";
|
||||||
public static final String lastnameParamName = "lastname";
|
public static final String lastnameParamName = "lastname";
|
||||||
@@ -138,7 +148,13 @@ public class ClientApiConstants {
|
|||||||
public static final String religionParamName = "religion";
|
public static final String religionParamName = "religion";
|
||||||
public static final String socialCategoryParamName = "socialCategory";
|
public static final String socialCategoryParamName = "socialCategory";
|
||||||
public static final String monthlyIncomeParamName = "monthlyIncome";
|
public static final String monthlyIncomeParamName = "monthlyIncome";
|
||||||
|
public static final String annualIncomeParamName = "annualIncome";
|
||||||
public static final String annualTurnoverParamName = "annualTurnover";
|
public static final String annualTurnoverParamName = "annualTurnover";
|
||||||
|
public static final String monthlyTurnoverParamName = "monthlyTurnover";
|
||||||
|
public static final String isSeniorCitizenParamName = "isSeniorCitizen";
|
||||||
|
public static final String isPwdParamName = "isPwd";
|
||||||
|
public static final String pwdCategoryIdParamName = "pwdCategoryId";
|
||||||
|
public static final String pwdCategoryParamName = "pwdCategory";
|
||||||
public static final String agricultureIncomeParamName = "agricultureIncome";
|
public static final String agricultureIncomeParamName = "agricultureIncome";
|
||||||
public static final String nonAgricultureIncomeParamName = "nonAgricultureIncome";
|
public static final String nonAgricultureIncomeParamName = "nonAgricultureIncome";
|
||||||
public static final String ageYearsParamName = "ageYears";
|
public static final String ageYearsParamName = "ageYears";
|
||||||
@@ -248,8 +264,9 @@ public class ClientApiConstants {
|
|||||||
staffOptionsParamName, dateOfBirthParamName, genderParamName, clientTypeParamName, clientClassificationParamName,
|
staffOptionsParamName, dateOfBirthParamName, genderParamName, clientTypeParamName, clientClassificationParamName,
|
||||||
salutationParamName, occupationCategoryParamName, occupationParamName, maritalStatusParamName,
|
salutationParamName, occupationCategoryParamName, occupationParamName, maritalStatusParamName,
|
||||||
educationLevelParamName, degreeQualificationParamName,
|
educationLevelParamName, degreeQualificationParamName,
|
||||||
religionParamName, socialCategoryParamName, monthlyIncomeParamName,
|
religionParamName, socialCategoryParamName, monthlyIncomeParamName, annualIncomeParamName,
|
||||||
annualTurnoverParamName, agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
annualTurnoverParamName, monthlyTurnoverParamName, agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
||||||
|
isSeniorCitizenParamName, isPwdParamName, pwdCategoryParamName,
|
||||||
ageYearsParamName, ageMonthsParamName, ageDaysParamName,
|
ageYearsParamName, ageMonthsParamName, ageDaysParamName,
|
||||||
nationalityIdParamName, isMinorParamName, guardianClientIdParamName, guardianClientDisplayNameParamName, guardianRelationParamName,
|
nationalityIdParamName, isMinorParamName, guardianClientIdParamName, guardianClientDisplayNameParamName, guardianRelationParamName,
|
||||||
dipVerifiedParamName, dipLookupLogIdParamName, dipTimestampParamName,
|
dipVerifiedParamName, dipLookupLogIdParamName, dipTimestampParamName,
|
||||||
|
|||||||
@@ -82,7 +82,13 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
private CodeValueData religion;
|
private CodeValueData religion;
|
||||||
private CodeValueData socialCategory;
|
private CodeValueData socialCategory;
|
||||||
private BigDecimal monthlyIncome;
|
private BigDecimal monthlyIncome;
|
||||||
|
private BigDecimal annualIncome;
|
||||||
private BigDecimal annualTurnover;
|
private BigDecimal annualTurnover;
|
||||||
|
private BigDecimal monthlyTurnover;
|
||||||
|
private Boolean isSeniorCitizen;
|
||||||
|
private Boolean isPwd;
|
||||||
|
private CodeValueData pwdCategory;
|
||||||
|
private Collection<CodeValueData> pwdCategoryOptions;
|
||||||
private BigDecimal agricultureIncome;
|
private BigDecimal agricultureIncome;
|
||||||
private BigDecimal nonAgricultureIncome;
|
private BigDecimal nonAgricultureIncome;
|
||||||
private Integer ageYears;
|
private Integer ageYears;
|
||||||
@@ -400,13 +406,19 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
data.degreeQualificationOptions = templateData.degreeQualificationOptions;
|
data.degreeQualificationOptions = templateData.degreeQualificationOptions;
|
||||||
data.religionOptions = templateData.religionOptions;
|
data.religionOptions = templateData.religionOptions;
|
||||||
data.socialCategoryOptions = templateData.socialCategoryOptions;
|
data.socialCategoryOptions = templateData.socialCategoryOptions;
|
||||||
|
data.pwdCategoryOptions = templateData.pwdCategoryOptions;
|
||||||
data.maritalStatus = clientData.maritalStatus;
|
data.maritalStatus = clientData.maritalStatus;
|
||||||
data.educationLevel = clientData.educationLevel;
|
data.educationLevel = clientData.educationLevel;
|
||||||
data.degreeQualification = clientData.degreeQualification;
|
data.degreeQualification = clientData.degreeQualification;
|
||||||
data.religion = clientData.religion;
|
data.religion = clientData.religion;
|
||||||
data.socialCategory = clientData.socialCategory;
|
data.socialCategory = clientData.socialCategory;
|
||||||
data.monthlyIncome = clientData.monthlyIncome;
|
data.monthlyIncome = clientData.monthlyIncome;
|
||||||
|
data.annualIncome = clientData.annualIncome;
|
||||||
data.annualTurnover = clientData.annualTurnover;
|
data.annualTurnover = clientData.annualTurnover;
|
||||||
|
data.monthlyTurnover = clientData.monthlyTurnover;
|
||||||
|
data.isSeniorCitizen = clientData.isSeniorCitizen;
|
||||||
|
data.isPwd = clientData.isPwd;
|
||||||
|
data.pwdCategory = clientData.pwdCategory;
|
||||||
data.agricultureIncome = clientData.agricultureIncome;
|
data.agricultureIncome = clientData.agricultureIncome;
|
||||||
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
||||||
data.ageYears = clientData.ageYears;
|
data.ageYears = clientData.ageYears;
|
||||||
@@ -456,13 +468,19 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
data.degreeQualificationOptions = clientData.degreeQualificationOptions;
|
data.degreeQualificationOptions = clientData.degreeQualificationOptions;
|
||||||
data.religionOptions = clientData.religionOptions;
|
data.religionOptions = clientData.religionOptions;
|
||||||
data.socialCategoryOptions = clientData.socialCategoryOptions;
|
data.socialCategoryOptions = clientData.socialCategoryOptions;
|
||||||
|
data.pwdCategoryOptions = clientData.pwdCategoryOptions;
|
||||||
data.maritalStatus = clientData.maritalStatus;
|
data.maritalStatus = clientData.maritalStatus;
|
||||||
data.educationLevel = clientData.educationLevel;
|
data.educationLevel = clientData.educationLevel;
|
||||||
data.degreeQualification = clientData.degreeQualification;
|
data.degreeQualification = clientData.degreeQualification;
|
||||||
data.religion = clientData.religion;
|
data.religion = clientData.religion;
|
||||||
data.socialCategory = clientData.socialCategory;
|
data.socialCategory = clientData.socialCategory;
|
||||||
data.monthlyIncome = clientData.monthlyIncome;
|
data.monthlyIncome = clientData.monthlyIncome;
|
||||||
|
data.annualIncome = clientData.annualIncome;
|
||||||
data.annualTurnover = clientData.annualTurnover;
|
data.annualTurnover = clientData.annualTurnover;
|
||||||
|
data.monthlyTurnover = clientData.monthlyTurnover;
|
||||||
|
data.isSeniorCitizen = clientData.isSeniorCitizen;
|
||||||
|
data.isPwd = clientData.isPwd;
|
||||||
|
data.pwdCategory = clientData.pwdCategory;
|
||||||
data.agricultureIncome = clientData.agricultureIncome;
|
data.agricultureIncome = clientData.agricultureIncome;
|
||||||
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
||||||
data.ageYears = clientData.ageYears;
|
data.ageYears = clientData.ageYears;
|
||||||
@@ -509,7 +527,12 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
data.religion = clientData.religion;
|
data.religion = clientData.religion;
|
||||||
data.socialCategory = clientData.socialCategory;
|
data.socialCategory = clientData.socialCategory;
|
||||||
data.monthlyIncome = clientData.monthlyIncome;
|
data.monthlyIncome = clientData.monthlyIncome;
|
||||||
|
data.annualIncome = clientData.annualIncome;
|
||||||
data.annualTurnover = clientData.annualTurnover;
|
data.annualTurnover = clientData.annualTurnover;
|
||||||
|
data.monthlyTurnover = clientData.monthlyTurnover;
|
||||||
|
data.isSeniorCitizen = clientData.isSeniorCitizen;
|
||||||
|
data.isPwd = clientData.isPwd;
|
||||||
|
data.pwdCategory = clientData.pwdCategory;
|
||||||
data.agricultureIncome = clientData.agricultureIncome;
|
data.agricultureIncome = clientData.agricultureIncome;
|
||||||
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
||||||
data.ageYears = clientData.ageYears;
|
data.ageYears = clientData.ageYears;
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "image_id")
|
@Column(name = "image_id")
|
||||||
private Long imageId;
|
private Long imageId;
|
||||||
|
|
||||||
|
@Column(name = "signature_id")
|
||||||
|
private Long signatureId;
|
||||||
|
|
||||||
@Column(name = "status_enum", nullable = false)
|
@Column(name = "status_enum", nullable = false)
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
@@ -231,9 +234,25 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "monthly_income")
|
@Column(name = "monthly_income")
|
||||||
private java.math.BigDecimal monthlyIncome;
|
private java.math.BigDecimal monthlyIncome;
|
||||||
|
|
||||||
|
@Column(name = "annual_income")
|
||||||
|
private java.math.BigDecimal annualIncome;
|
||||||
|
|
||||||
@Column(name = "annual_turnover")
|
@Column(name = "annual_turnover")
|
||||||
private java.math.BigDecimal annualTurnover;
|
private java.math.BigDecimal annualTurnover;
|
||||||
|
|
||||||
|
@Column(name = "monthly_turnover")
|
||||||
|
private java.math.BigDecimal monthlyTurnover;
|
||||||
|
|
||||||
|
@Column(name = "is_senior_citizen")
|
||||||
|
private Boolean isSeniorCitizen;
|
||||||
|
|
||||||
|
@Column(name = "is_pwd")
|
||||||
|
private Boolean isPwd;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "pwd_category_cv_id")
|
||||||
|
private CodeValue pwdCategory;
|
||||||
|
|
||||||
@Column(name = "agriculture_income")
|
@Column(name = "agriculture_income")
|
||||||
private java.math.BigDecimal agricultureIncome;
|
private java.math.BigDecimal agricultureIncome;
|
||||||
|
|
||||||
@@ -598,6 +617,10 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return this.office.getId();
|
return this.office.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return this.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
public String mobileNo() {
|
public String mobileNo() {
|
||||||
return this.mobileNo;
|
return this.mobileNo;
|
||||||
}
|
}
|
||||||
@@ -832,6 +855,14 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return this.socialCategory != null ? this.socialCategory.getId() : null;
|
return this.socialCategory != null ? this.socialCategory.getId() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long pwdCategoryId() {
|
||||||
|
return this.pwdCategory != null ? this.pwdCategory.getId() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePwdCategory(CodeValue pwdCategory) {
|
||||||
|
this.pwdCategory = pwdCategory;
|
||||||
|
}
|
||||||
|
|
||||||
public void updateSocialCategory(CodeValue socialCategory) {
|
public void updateSocialCategory(CodeValue socialCategory) {
|
||||||
this.socialCategory = socialCategory;
|
this.socialCategory = socialCategory;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -29,6 +29,7 @@ import lombok.Getter;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Form 60 declaration as per Rule 114B of Income Tax Rules, 1962.
|
* Form 60 declaration as per Rule 114B of Income Tax Rules, 1962.
|
||||||
@@ -87,7 +88,7 @@ public class ClientForm60 extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
form.estimatedTotalIncome = command.bigDecimalValueOfParameterNamed("estimatedTotalIncome");
|
form.estimatedTotalIncome = command.bigDecimalValueOfParameterNamed("estimatedTotalIncome");
|
||||||
form.reasonForNoPan = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamed("reasonForNoPan"), null);
|
form.reasonForNoPan = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamed("reasonForNoPan"), null);
|
||||||
form.declaration = command.booleanPrimitiveValueOfParameterNamed("declaration");
|
form.declaration = command.booleanPrimitiveValueOfParameterNamed("declaration");
|
||||||
form.submittedOn = LocalDate.now();
|
form.submittedOn = DateUtils.getLocalDateOfTenant();
|
||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-10
@@ -32,6 +32,7 @@ import java.util.Map;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a client's KYC (Know Your Customer) record as mandated by the Reserve Bank of India
|
* Represents a client's KYC (Know Your Customer) record as mandated by the Reserve Bank of India
|
||||||
@@ -260,7 +261,7 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
this.gstinVerified = false;
|
this.gstinVerified = false;
|
||||||
this.cinVerified = false;
|
this.cinVerified = false;
|
||||||
this.dinVerified = false;
|
this.dinVerified = false;
|
||||||
this.initiatedOn = LocalDate.now();
|
this.initiatedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClientKyc initiateKyc(final Client client, final JsonCommand command) {
|
public static ClientKyc initiateKyc(final Client client, final JsonCommand command) {
|
||||||
@@ -456,13 +457,13 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public void markCkycUploaded(final String ckycNumber, final String batchId) {
|
public void markCkycUploaded(final String ckycNumber, final String batchId) {
|
||||||
this.ckycNumber = StringUtils.defaultIfEmpty(ckycNumber, null);
|
this.ckycNumber = StringUtils.defaultIfEmpty(ckycNumber, null);
|
||||||
this.ckycBatchId = StringUtils.defaultIfEmpty(batchId, null);
|
this.ckycBatchId = StringUtils.defaultIfEmpty(batchId, null);
|
||||||
this.ckycUploadedOn = LocalDate.now();
|
this.ckycUploadedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called after successful 3rd-party verification of all required documents. */
|
/** Called after successful 3rd-party verification of all required documents. */
|
||||||
public void markVerified(final String ckycNumber) {
|
public void markVerified(final String ckycNumber) {
|
||||||
this.kycStatus = ClientKycStatus.VERIFIED.getValue();
|
this.kycStatus = ClientKycStatus.VERIFIED.getValue();
|
||||||
this.verifiedOn = LocalDate.now();
|
this.verifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
this.ckycNumber = StringUtils.defaultIfEmpty(ckycNumber, null);
|
this.ckycNumber = StringUtils.defaultIfEmpty(ckycNumber, null);
|
||||||
|
|
||||||
final KycRiskCategory risk = KycRiskCategory.fromInt(this.riskCategory);
|
final KycRiskCategory risk = KycRiskCategory.fromInt(this.riskCategory);
|
||||||
@@ -474,14 +475,14 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
/** Called when verification fails. */
|
/** Called when verification fails. */
|
||||||
public void markRejected(final String remarks) {
|
public void markRejected(final String remarks) {
|
||||||
this.kycStatus = ClientKycStatus.REJECTED.getValue();
|
this.kycStatus = ClientKycStatus.REJECTED.getValue();
|
||||||
this.rejectedOn = LocalDate.now();
|
this.rejectedOn = DateUtils.getLocalDateOfTenant();
|
||||||
this.remarks = StringUtils.defaultIfEmpty(remarks, this.remarks);
|
this.remarks = StringUtils.defaultIfEmpty(remarks, this.remarks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markPanVerified(final String fullName, final String firstName, final String lastName,
|
public void markPanVerified(final String fullName, final String firstName, final String lastName,
|
||||||
final String aadhaarSeedingStatus, final String category, final String status) {
|
final String aadhaarSeedingStatus, final String category, final String status) {
|
||||||
this.panVerified = true;
|
this.panVerified = true;
|
||||||
this.panVerifiedOn = LocalDate.now();
|
this.panVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
this.panFullName = StringUtils.defaultIfEmpty(fullName, null);
|
this.panFullName = StringUtils.defaultIfEmpty(fullName, null);
|
||||||
this.panFirstName = StringUtils.defaultIfEmpty(firstName, null);
|
this.panFirstName = StringUtils.defaultIfEmpty(firstName, null);
|
||||||
this.panLastName = StringUtils.defaultIfEmpty(lastName, null);
|
this.panLastName = StringUtils.defaultIfEmpty(lastName, null);
|
||||||
@@ -507,28 +508,28 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
public void markAadhaarVerified() {
|
public void markAadhaarVerified() {
|
||||||
this.aadhaarVerified = true;
|
this.aadhaarVerified = true;
|
||||||
this.aadhaarVerifiedOn = LocalDate.now();
|
this.aadhaarVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markGstinVerified() {
|
public void markGstinVerified() {
|
||||||
this.gstinVerified = true;
|
this.gstinVerified = true;
|
||||||
this.gstinVerifiedOn = LocalDate.now();
|
this.gstinVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markCinVerified() {
|
public void markCinVerified() {
|
||||||
this.cinVerified = true;
|
this.cinVerified = true;
|
||||||
this.cinVerifiedOn = LocalDate.now();
|
this.cinVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markDinVerified() {
|
public void markDinVerified() {
|
||||||
this.dinVerified = true;
|
this.dinVerified = true;
|
||||||
this.dinVerifiedOn = LocalDate.now();
|
this.dinVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markDinVerified(final String dinNumber, final String mcaResponseJson) {
|
public void markDinVerified(final String dinNumber, final String mcaResponseJson) {
|
||||||
this.dinNumber = dinNumber;
|
this.dinNumber = dinNumber;
|
||||||
this.dinVerified = true;
|
this.dinVerified = true;
|
||||||
this.dinVerifiedOn = LocalDate.now();
|
this.dinVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
this.isDirector = true;
|
this.isDirector = true;
|
||||||
this.dinMcaResponse = mcaResponseJson;
|
this.dinMcaResponse = mcaResponseJson;
|
||||||
}
|
}
|
||||||
@@ -585,6 +586,15 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return riskCategory;
|
return riskCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directly sets the risk category outside the normal command-diff update path. Used by the AML
|
||||||
|
* module to auto-escalate a client's risk category after their 3rd cumulative STR case -
|
||||||
|
* every such change is recorded in {@code m_client_risk_category_history} by the caller.
|
||||||
|
*/
|
||||||
|
public void escalateRiskCategory(Integer newCategory) {
|
||||||
|
this.riskCategory = newCategory;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPanNumber() {
|
public String getPanNumber() {
|
||||||
return panNumber;
|
return panNumber;
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -55,6 +55,14 @@ public interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecif
|
|||||||
@Query("UPDATE Client client SET client.imageId = null WHERE client.id = :clientId")
|
@Query("UPDATE Client client SET client.imageId = null WHERE client.id = :clientId")
|
||||||
void removeImageId(@Param("clientId") Long clientId);
|
void removeImageId(@Param("clientId") Long clientId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE Client client SET client.signatureId = :signatureId WHERE client.id = :clientId")
|
||||||
|
void updateByIdAndSignatureId(@Param("clientId") Long clientId, @Param("signatureId") Long signatureId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE Client client SET client.signatureId = null WHERE client.id = :clientId")
|
||||||
|
void removeSignatureId(@Param("clientId") Long clientId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count clients whose cif_number begins with the given prefix. Used by the CIF number generator
|
* Count clients whose cif_number begins with the given prefix. Used by the CIF number generator
|
||||||
* (TTYYYYNNNNNN format) to compute the next sequence within a given (type, year) bucket.
|
* (TTYYYYNNNNNN format) to compute the next sequence within a given (type, year) bucket.
|
||||||
|
|||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.client.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_client_risk_rule")
|
||||||
|
public class ClientRiskRule {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "rule_name", nullable = false, length = 100)
|
||||||
|
private String ruleName;
|
||||||
|
|
||||||
|
@Column(name = "legal_form_id")
|
||||||
|
private Integer legalFormId;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "occupation_category_cv_id")
|
||||||
|
private CodeValue occupationCategory;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "occupation_cv_id")
|
||||||
|
private CodeValue occupation;
|
||||||
|
|
||||||
|
@Column(name = "min_monthly_income", precision = 19, scale = 6)
|
||||||
|
private BigDecimal minMonthlyIncome;
|
||||||
|
|
||||||
|
@Column(name = "max_monthly_income", precision = 19, scale = 6)
|
||||||
|
private BigDecimal maxMonthlyIncome;
|
||||||
|
|
||||||
|
@Column(name = "min_annual_income", precision = 19, scale = 6)
|
||||||
|
private BigDecimal minAnnualIncome;
|
||||||
|
|
||||||
|
@Column(name = "max_annual_income", precision = 19, scale = 6)
|
||||||
|
private BigDecimal maxAnnualIncome;
|
||||||
|
|
||||||
|
@Column(name = "min_annual_turnover", precision = 19, scale = 6)
|
||||||
|
private BigDecimal minAnnualTurnover;
|
||||||
|
|
||||||
|
@Column(name = "max_annual_turnover", precision = 19, scale = 6)
|
||||||
|
private BigDecimal maxAnnualTurnover;
|
||||||
|
|
||||||
|
@Column(name = "risk_category", nullable = false)
|
||||||
|
private Integer riskCategory;
|
||||||
|
|
||||||
|
@Column(name = "reason", nullable = false, length = 500)
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
@Column(name = "aml_check_status", length = 20)
|
||||||
|
private String amlCheckStatus;
|
||||||
|
|
||||||
|
@Column(name = "banned_entity_match", length = 10)
|
||||||
|
private String bannedEntityMatch;
|
||||||
|
|
||||||
|
@Column(name = "willful_defaulter", length = 10)
|
||||||
|
private String willfulDefaulter;
|
||||||
|
|
||||||
|
@Column(name = "ecgc_sal_flag", length = 10)
|
||||||
|
private String ecgcSalFlag;
|
||||||
|
|
||||||
|
@Column(name = "dormant_flag", length = 20)
|
||||||
|
private String dormantFlag;
|
||||||
|
|
||||||
|
@Column(name = "order_position", nullable = false)
|
||||||
|
private Integer orderPosition;
|
||||||
|
|
||||||
|
protected ClientRiskRule() {}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRuleName() {
|
||||||
|
return ruleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLegalFormId() {
|
||||||
|
return legalFormId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeValue getOccupationCategory() {
|
||||||
|
return occupationCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeValue getOccupation() {
|
||||||
|
return occupation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMinMonthlyIncome() {
|
||||||
|
return minMonthlyIncome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMaxMonthlyIncome() {
|
||||||
|
return maxMonthlyIncome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMinAnnualIncome() {
|
||||||
|
return minAnnualIncome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMaxAnnualIncome() {
|
||||||
|
return maxAnnualIncome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMinAnnualTurnover() {
|
||||||
|
return minAnnualTurnover;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMaxAnnualTurnover() {
|
||||||
|
return maxAnnualTurnover;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRiskCategory() {
|
||||||
|
return riskCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOrderPosition() {
|
||||||
|
return orderPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAmlCheckStatus() {
|
||||||
|
return amlCheckStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBannedEntityMatch() {
|
||||||
|
return bannedEntityMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWillfulDefaulter() {
|
||||||
|
return willfulDefaulter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEcgcSalFlag() {
|
||||||
|
return ecgcSalFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDormantFlag() {
|
||||||
|
return dormantFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -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.client.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
|
||||||
|
public interface ClientRiskRuleRepository extends JpaRepository<ClientRiskRule, Long> {
|
||||||
|
|
||||||
|
@Query("SELECT r FROM ClientRiskRule r ORDER BY r.orderPosition ASC")
|
||||||
|
List<ClientRiskRule> findAllOrderedByOrderPosition();
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* 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.currentaccount.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-side view of a single pending current account Nominee/Digital-Feature edit
|
||||||
|
* (m_current_account_pending_edit), for the Approvals -> Pending Current Account Edits screen.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class CurrentAccountPendingEditData implements Serializable {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long currentAccountId;
|
||||||
|
private String accountNo;
|
||||||
|
private Long makerId;
|
||||||
|
private String makerUsername;
|
||||||
|
private Long checkerId;
|
||||||
|
private String checkerUsername;
|
||||||
|
private LocalDateTime createdOn;
|
||||||
|
private LocalDateTime checkedOn;
|
||||||
|
private String status;
|
||||||
|
private String rejectReason;
|
||||||
|
private BigDecimal holdAmount;
|
||||||
|
private boolean facilityConfigSubmitted;
|
||||||
|
private boolean nomineeSubmitted;
|
||||||
|
private Map<String, Object> proposedFacilityConfig;
|
||||||
|
private Map<String, Object> proposedNominee;
|
||||||
|
|
||||||
|
public CurrentAccountPendingEditData() {}
|
||||||
|
}
|
||||||
+2
-1
@@ -24,6 +24,7 @@ import jakarta.persistence.Table;
|
|||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity log for each DIP / MNRL mobile number lookup request.
|
* Activity log for each DIP / MNRL mobile number lookup request.
|
||||||
@@ -78,6 +79,6 @@ public class MobileLookupLog extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
this.rawResponse = rawResponse;
|
this.rawResponse = rawResponse;
|
||||||
this.lookupSource = lookupSource;
|
this.lookupSource = lookupSource;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.lookedUpOn = OffsetDateTime.now();
|
this.lookedUpOn = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -18,7 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.sanctions.domain;
|
package org.apache.fineract.portfolio.sanctions.domain;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
@@ -40,4 +42,11 @@ public interface SanctionsEntryRepository extends JpaRepository<SanctionsEntry,
|
|||||||
void deleteAllByActiveTrue();
|
void deleteAllByActiveTrue();
|
||||||
|
|
||||||
long countByActiveTrue();
|
long countByActiveTrue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp the active sanctions list was last loaded into the database — i.e. the data
|
||||||
|
* currency shown to callers so they know which list snapshot a screening ran against.
|
||||||
|
*/
|
||||||
|
@Query("SELECT MAX(s.createdDate) FROM SanctionsEntry s WHERE s.active = true")
|
||||||
|
Optional<OffsetDateTime> findListLastLoadedOn();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -23,6 +23,7 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity log for each sanctions screening request.
|
* Activity log for each sanctions screening request.
|
||||||
@@ -73,7 +74,7 @@ public class SanctionsScreeningLog extends AbstractAuditableWithUTCDateTimeCusto
|
|||||||
this.screeningSource = screeningSource;
|
this.screeningSource = screeningSource;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.ipAddress = ipAddress;
|
this.ipAddress = ipAddress;
|
||||||
this.screenedOn = OffsetDateTime.now();
|
this.screenedOn = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Accessors ─────────────────────────────────────────────────────────────
|
// ── Accessors ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+6
-1
@@ -224,7 +224,7 @@ public final class DepositsApiConstants {
|
|||||||
* Deposit Product Parameters
|
* Deposit Product Parameters
|
||||||
*/
|
*/
|
||||||
private static final Set<String> DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
|
private static final Set<String> DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
|
||||||
monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
|
dateFormatParamName, monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
|
||||||
digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName,
|
digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName,
|
||||||
interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
|
interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
|
||||||
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, accountingRuleParamName, chargesParamName,
|
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, accountingRuleParamName, chargesParamName,
|
||||||
@@ -512,6 +512,11 @@ public final class DepositsApiConstants {
|
|||||||
fixedDepositInterestCalculationData.add(tenureInMonthsParamName);
|
fixedDepositInterestCalculationData.add(tenureInMonthsParamName);
|
||||||
fixedDepositInterestCalculationData.add(interestPostingPeriodInMonthsParamName);
|
fixedDepositInterestCalculationData.add(interestPostingPeriodInMonthsParamName);
|
||||||
fixedDepositInterestCalculationData.add(interestCompoundingPeriodInMonthsParamName);
|
fixedDepositInterestCalculationData.add(interestCompoundingPeriodInMonthsParamName);
|
||||||
|
// Exact calendar-based calculation params
|
||||||
|
fixedDepositInterestCalculationData.add("submittedOnDate");
|
||||||
|
fixedDepositInterestCalculationData.add("dateFormat");
|
||||||
|
fixedDepositInterestCalculationData.add("locale");
|
||||||
|
fixedDepositInterestCalculationData.add("daysInYear");
|
||||||
|
|
||||||
return fixedDepositInterestCalculationData;
|
return fixedDepositInterestCalculationData;
|
||||||
|
|
||||||
|
|||||||
+1
@@ -170,6 +170,7 @@ public final class SavingsAccountData implements Serializable {
|
|||||||
private Boolean balanceSwapOption;
|
private Boolean balanceSwapOption;
|
||||||
private Boolean msme;
|
private Boolean msme;
|
||||||
private Boolean autoDebitFacility;
|
private Boolean autoDebitFacility;
|
||||||
|
private Boolean terminateTrue;
|
||||||
private String avgBalanceCalcPeriod;
|
private String avgBalanceCalcPeriod;
|
||||||
private String validForPeriod;
|
private String validForPeriod;
|
||||||
private Boolean odAccountClosedZeroBalanceInDayClosing;
|
private Boolean odAccountClosedZeroBalanceInDayClosing;
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.savings.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-side view of a single pending savings account Nominee/Digital-Feature edit
|
||||||
|
* (m_savings_account_pending_edit), for the Approvals -> Pending Savings Account Edits screen.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class SavingsAccountPendingEditData implements Serializable {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long savingsAccountId;
|
||||||
|
private String accountNo;
|
||||||
|
private Long makerId;
|
||||||
|
private String makerUsername;
|
||||||
|
private Long checkerId;
|
||||||
|
private String checkerUsername;
|
||||||
|
private LocalDateTime createdOn;
|
||||||
|
private LocalDateTime checkedOn;
|
||||||
|
private String status;
|
||||||
|
private String rejectReason;
|
||||||
|
private BigDecimal holdAmount;
|
||||||
|
private boolean facilityConfigSubmitted;
|
||||||
|
private boolean nomineeSubmitted;
|
||||||
|
private Map<String, Object> proposedFacilityConfig;
|
||||||
|
private Map<String, Object> proposedNominee;
|
||||||
|
private String depositAccountType;
|
||||||
|
|
||||||
|
public SavingsAccountPendingEditData() {}
|
||||||
|
}
|
||||||
+720
-714
File diff suppressed because it is too large
Load Diff
+49
@@ -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.savings.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-side view of a single pending Savings Product edit row (m_savings_product_temp), for the Approvals ->
|
||||||
|
* Pending Savings Product Edits screen.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class SavingsProductTempData implements Serializable {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long savingsProductId;
|
||||||
|
private String savingsProductName;
|
||||||
|
private Long makerId;
|
||||||
|
private String makerUsername;
|
||||||
|
private Long checkerId;
|
||||||
|
private String checkerUsername;
|
||||||
|
private LocalDateTime createdOn;
|
||||||
|
private LocalDateTime checkedOn;
|
||||||
|
private String status;
|
||||||
|
private String rejectReason;
|
||||||
|
private Map<String, Object> proposedChanges;
|
||||||
|
|
||||||
|
public SavingsProductTempData() {}
|
||||||
|
}
|
||||||
+7
-1
@@ -36,7 +36,8 @@ public enum SavingsAccountStatusType {
|
|||||||
REJECTED(500, "savingsAccountStatusType.rejected"), //
|
REJECTED(500, "savingsAccountStatusType.rejected"), //
|
||||||
CLOSED(600, "savingsAccountStatusType.closed"), //
|
CLOSED(600, "savingsAccountStatusType.closed"), //
|
||||||
PRE_MATURE_CLOSURE(700, "savingsAccountStatusType.pre.mature.closure"), //
|
PRE_MATURE_CLOSURE(700, "savingsAccountStatusType.pre.mature.closure"), //
|
||||||
MATURED(800, "savingsAccountStatusType.matured"); //
|
MATURED(800, "savingsAccountStatusType.matured"), //
|
||||||
|
TERMINATED(900, "savingsAccountStatusType.terminated"); //
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
@@ -53,6 +54,7 @@ public enum SavingsAccountStatusType {
|
|||||||
case 600 -> SavingsAccountStatusType.CLOSED;
|
case 600 -> SavingsAccountStatusType.CLOSED;
|
||||||
case 700 -> SavingsAccountStatusType.PRE_MATURE_CLOSURE;
|
case 700 -> SavingsAccountStatusType.PRE_MATURE_CLOSURE;
|
||||||
case 800 -> SavingsAccountStatusType.MATURED;
|
case 800 -> SavingsAccountStatusType.MATURED;
|
||||||
|
case 900 -> SavingsAccountStatusType.TERMINATED;
|
||||||
default -> SavingsAccountStatusType.INVALID;
|
default -> SavingsAccountStatusType.INVALID;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -113,4 +115,8 @@ public enum SavingsAccountStatusType {
|
|||||||
public boolean isPreMatureClosure() {
|
public boolean isPreMatureClosure() {
|
||||||
return this.value.equals(SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue());
|
return this.value.equals(SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return this.value.equals(SavingsAccountStatusType.TERMINATED.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -285,6 +285,11 @@ public final class SavingsEnumerations {
|
|||||||
SavingsAccountStatusType.MATURED.getCode(), "Matured", submittedAndPendingApproval, isApproved, isRejected,
|
SavingsAccountStatusType.MATURED.getCode(), "Matured", submittedAndPendingApproval, isApproved, isRejected,
|
||||||
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
|
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
|
||||||
break;
|
break;
|
||||||
|
case TERMINATED:
|
||||||
|
optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.TERMINATED.getValue().longValue(),
|
||||||
|
SavingsAccountStatusType.TERMINATED.getCode(), "Terminated", submittedAndPendingApproval, isApproved, isRejected,
|
||||||
|
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return optionData;
|
return optionData;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.search.data;
|
package org.apache.fineract.portfolio.search.data;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
import org.apache.fineract.portfolio.search.SearchConstants.SearchSupportedResources;
|
import org.apache.fineract.portfolio.search.SearchConstants.SearchSupportedResources;
|
||||||
|
|
||||||
public class SearchConditions {
|
public class SearchConditions {
|
||||||
@@ -37,17 +38,17 @@ public class SearchConditions {
|
|||||||
this.searchResource = searchResource;
|
this.searchResource = searchResource;
|
||||||
this.exactMatch = exactMatch;
|
this.exactMatch = exactMatch;
|
||||||
this.clientSearch = null == searchResource
|
this.clientSearch = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.CLIENTS.name().toLowerCase(Locale.ROOT));
|
||||||
this.groupSearch = null == searchResource
|
this.groupSearch = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.GROUPS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.GROUPS.name().toLowerCase(Locale.ROOT));
|
||||||
this.loanSeach = null == searchResource
|
this.loanSeach = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.LOANS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.LOANS.name().toLowerCase(Locale.ROOT));
|
||||||
this.savingSeach = null == searchResource
|
this.savingSeach = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.SAVINGS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.SAVINGS.name().toLowerCase(Locale.ROOT));
|
||||||
this.shareSeach = null == searchResource
|
this.shareSeach = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.SHARES.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.SHARES.name().toLowerCase(Locale.ROOT));
|
||||||
this.clientIdentifierSearch = null == searchResource
|
this.clientIdentifierSearch = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTIDENTIFIERS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.CLIENTIDENTIFIERS.name().toLowerCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchConditions(final String searchQueryParam, final String searchResource, final Boolean clientSearch,
|
public SearchConditions(final String searchQueryParam, final String searchResource, final Boolean clientSearch,
|
||||||
|
|||||||
+1
-1
@@ -321,6 +321,6 @@ public class SearchUtil {
|
|||||||
|
|
||||||
public String camelToSnake(final String camelStr) {
|
public String camelToSnake(final String camelStr) {
|
||||||
return camelStr == null ? null
|
return camelStr == null ? null
|
||||||
: camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
|
: camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* 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.useradministration.agreement.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.PrePersist;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A versioned, admin-manageable "user agreement" (bank policy/warning) that every user must accept right after
|
||||||
|
* logging in. Only one row may be {@link #active} at a time; enforced transactionally by the write service, not a
|
||||||
|
* DB constraint.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_user_agreement", uniqueConstraints = { @UniqueConstraint(columnNames = "version", name = "uq_user_agreement_version") })
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UserAgreement extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@Column(name = "version", nullable = false)
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
@Column(name = "title", length = 200, nullable = false)
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Column(name = "body", nullable = false)
|
||||||
|
private String body;
|
||||||
|
|
||||||
|
@Column(name = "is_active", nullable = false)
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@JoinColumn(name = "created_by", nullable = false)
|
||||||
|
private AppUser createdBy;
|
||||||
|
|
||||||
|
@Column(name = "created_on", nullable = false, updatable = false)
|
||||||
|
private OffsetDateTime createdOn;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
private void prePersist() {
|
||||||
|
if (createdOn == null) {
|
||||||
|
createdOn = OffsetDateTime.now(ZoneOffset.UTC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserAgreement create(final Integer version, final String title, final String body, final AppUser createdBy) {
|
||||||
|
final UserAgreement agreement = new UserAgreement();
|
||||||
|
agreement.version = version;
|
||||||
|
agreement.title = title;
|
||||||
|
agreement.body = body;
|
||||||
|
agreement.createdBy = createdBy;
|
||||||
|
agreement.active = true;
|
||||||
|
return agreement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void activate() {
|
||||||
|
this.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deactivate() {
|
||||||
|
this.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* 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.useradministration.agreement.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.PrePersist;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audit-proof record of a user accepting a given version of the {@link UserAgreement} right after logging in.
|
||||||
|
* {@link #agreementVersion} is a denormalized snapshot of {@code UserAgreement.version} at acceptance time, so the
|
||||||
|
* acceptance record survives future version bumps unaffected.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_user_agreement_acceptance", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(columnNames = { "appuser_id", "agreement_id" }, name = "uq_user_agreement_acceptance") })
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UserAgreementAcceptance extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@JoinColumn(name = "appuser_id", nullable = false)
|
||||||
|
private AppUser appUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@JoinColumn(name = "agreement_id", nullable = false)
|
||||||
|
private UserAgreement agreement;
|
||||||
|
|
||||||
|
@Column(name = "agreement_version", nullable = false)
|
||||||
|
private Integer agreementVersion;
|
||||||
|
|
||||||
|
@Column(name = "accepted_on", nullable = false, updatable = false)
|
||||||
|
private OffsetDateTime acceptedOn;
|
||||||
|
|
||||||
|
@Column(name = "ip_address", length = 45)
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
|
@Column(name = "user_agent", length = 500)
|
||||||
|
private String userAgent;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
private void prePersist() {
|
||||||
|
if (acceptedOn == null) {
|
||||||
|
acceptedOn = OffsetDateTime.now(ZoneOffset.UTC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserAgreementAcceptance create(final AppUser appUser, final UserAgreement agreement, final String ipAddress,
|
||||||
|
final String userAgent) {
|
||||||
|
final UserAgreementAcceptance acceptance = new UserAgreementAcceptance();
|
||||||
|
acceptance.appUser = appUser;
|
||||||
|
acceptance.agreement = agreement;
|
||||||
|
acceptance.agreementVersion = agreement.getVersion();
|
||||||
|
acceptance.ipAddress = ipAddress;
|
||||||
|
acceptance.userAgent = userAgent;
|
||||||
|
return acceptance;
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.useradministration.agreement.domain;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface UserAgreementAcceptanceRepository extends JpaRepository<UserAgreementAcceptance, Long> {
|
||||||
|
|
||||||
|
boolean existsByAppUser_IdAndAgreement_Id(Long appUserId, Long agreementId);
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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.useradministration.agreement.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface UserAgreementRepository extends JpaRepository<UserAgreement, Long> {
|
||||||
|
|
||||||
|
Optional<UserAgreement> findByActiveTrue();
|
||||||
|
|
||||||
|
List<UserAgreement> findAllByOrderByVersionDesc();
|
||||||
|
}
|
||||||
+1
-1
@@ -39,7 +39,7 @@ public interface BankUserDscRepository extends JpaRepository<BankUserDsc, Long>
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Login gate: returns true if the user has at least one usable DSC mapping that's also not
|
* Login gate: returns true if the user has at least one usable DSC mapping that's also not
|
||||||
* past its notAfter date. The {@code dscExpiryDate >= today} clause prevents 2FA-gating a
|
* past its notAfter date. The {@code dscExpiryDate >= today} clause prevents 2FA-gating a
|
||||||
* user who's enrolled but whose cert silently expired between log-ins — they'd otherwise be
|
* user who's enrolled but whose cert silently expired between log-ins — they'd otherwise be
|
||||||
* stuck in a verification loop with a cert the helper would sign with but the server would
|
* stuck in a verification loop with a cert the helper would sign with but the server would
|
||||||
* accept (the verify side filters too, but the gate is what kicks the user into the DSC flow
|
* accept (the verify side filters too, but the gate is what kicks the user into the DSC flow
|
||||||
|
|||||||
+52
-5
@@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
|
|||||||
import org.apache.fineract.organisation.office.domain.Office;
|
import org.apache.fineract.organisation.office.domain.Office;
|
||||||
import org.apache.fineract.organisation.staff.domain.Staff;
|
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_current_account", uniqueConstraints = {
|
@Table(name = "m_current_account", uniqueConstraints = {
|
||||||
@@ -102,6 +103,30 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
|||||||
@Column(name = "closedon_date")
|
@Column(name = "closedon_date")
|
||||||
private LocalDate closedOnDate;
|
private LocalDate closedOnDate;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "submittedon_userid")
|
||||||
|
private AppUser submittedOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "approvedon_userid")
|
||||||
|
private AppUser approvedOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "activatedon_userid")
|
||||||
|
private AppUser activatedOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "rejectedon_userid")
|
||||||
|
private AppUser rejectedOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "withdrawnon_userid")
|
||||||
|
private AppUser withdrawnOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "closedon_userid")
|
||||||
|
private AppUser closedOnByUser;
|
||||||
|
|
||||||
// Currency (copied from product at account creation)
|
// Currency (copied from product at account creation)
|
||||||
@Column(name = "currency_code", nullable = false, length = 3)
|
@Column(name = "currency_code", nullable = false, length = 3)
|
||||||
private String currencyCode;
|
private String currencyCode;
|
||||||
@@ -304,6 +329,12 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
|||||||
@Column(name = "od_account_closed_zero_balance_in_day_closing")
|
@Column(name = "od_account_closed_zero_balance_in_day_closing")
|
||||||
private boolean odAccountClosedZeroBalanceInDayClosing = false;
|
private boolean odAccountClosedZeroBalanceInDayClosing = false;
|
||||||
|
|
||||||
|
@Column(name = "is_linking_enable", nullable = false)
|
||||||
|
private boolean shareLinkingEnabled = false;
|
||||||
|
|
||||||
|
@Column(name = "terminate_true", nullable = false, columnDefinition = "boolean default false")
|
||||||
|
private boolean terminateTrue = false;
|
||||||
|
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
@@ -373,29 +404,34 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
|||||||
|
|
||||||
public void updateAccountNo(String accountNo) { this.accountNo = accountNo; }
|
public void updateAccountNo(String accountNo) { this.accountNo = accountNo; }
|
||||||
|
|
||||||
public void approve(LocalDate approvedOnDate) {
|
public void approve(LocalDate approvedOnDate, AppUser approvedOnByUser) {
|
||||||
this.statusEnum = 200;
|
this.statusEnum = 200;
|
||||||
this.approvedOnDate = approvedOnDate;
|
this.approvedOnDate = approvedOnDate;
|
||||||
|
this.approvedOnByUser = approvedOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void activate(LocalDate activatedOnDate) {
|
public void activate(LocalDate activatedOnDate, AppUser activatedOnByUser) {
|
||||||
this.statusEnum = 300;
|
this.statusEnum = 300;
|
||||||
this.activatedOnDate = activatedOnDate;
|
this.activatedOnDate = activatedOnDate;
|
||||||
|
this.activatedOnByUser = activatedOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reject(LocalDate rejectedOnDate) {
|
public void reject(LocalDate rejectedOnDate, AppUser rejectedOnByUser) {
|
||||||
this.statusEnum = 500;
|
this.statusEnum = 500;
|
||||||
this.rejectedOnDate = rejectedOnDate;
|
this.rejectedOnDate = rejectedOnDate;
|
||||||
|
this.rejectedOnByUser = rejectedOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void withdraw(LocalDate withdrawnOnDate) {
|
public void withdraw(LocalDate withdrawnOnDate, AppUser withdrawnOnByUser) {
|
||||||
this.statusEnum = 400;
|
this.statusEnum = 400;
|
||||||
this.withdrawnOnDate = withdrawnOnDate;
|
this.withdrawnOnDate = withdrawnOnDate;
|
||||||
|
this.withdrawnOnByUser = withdrawnOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close(LocalDate closedOnDate) {
|
public void close(LocalDate closedOnDate, AppUser closedOnByUser) {
|
||||||
this.statusEnum = 600;
|
this.statusEnum = 600;
|
||||||
this.closedOnDate = closedOnDate;
|
this.closedOnDate = closedOnDate;
|
||||||
|
this.closedOnByUser = closedOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMinRequiredOpeningBalance(java.math.BigDecimal val) { this.minRequiredOpeningBalance = val; }
|
public void setMinRequiredOpeningBalance(java.math.BigDecimal val) { this.minRequiredOpeningBalance = val; }
|
||||||
@@ -406,4 +442,15 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
|||||||
public void setNominalAnnualInterestRateOverdraft(java.math.BigDecimal val) { this.nominalAnnualInterestRateOverdraft = val; }
|
public void setNominalAnnualInterestRateOverdraft(java.math.BigDecimal val) { this.nominalAnnualInterestRateOverdraft = val; }
|
||||||
public void setWithdrawalFeeForTransfer(boolean val) { this.withdrawalFeeForTransfer = val; }
|
public void setWithdrawalFeeForTransfer(boolean val) { this.withdrawalFeeForTransfer = val; }
|
||||||
public void setMinOverdraftForInterestCalculation(java.math.BigDecimal val) { this.minOverdraftForInterestCalculation = val; }
|
public void setMinOverdraftForInterestCalculation(java.math.BigDecimal val) { this.minOverdraftForInterestCalculation = val; }
|
||||||
|
|
||||||
|
public boolean isShareLinkingEnabled() { return shareLinkingEnabled; }
|
||||||
|
public void setShareLinkingEnabled(boolean shareLinkingEnabled) { this.shareLinkingEnabled = shareLinkingEnabled; }
|
||||||
|
|
||||||
|
public boolean isTerminateTrue() { return terminateTrue; }
|
||||||
|
public void setTerminateTrue(boolean terminateTrue) { this.terminateTrue = terminateTrue; }
|
||||||
|
|
||||||
|
public void terminate() {
|
||||||
|
this.terminateTrue = true;
|
||||||
|
this.statusEnum = 900;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
@@ -99,4 +99,31 @@ public class CurrentAccountFacilityConfig extends AbstractPersistableCustom<Long
|
|||||||
cfg.currentAccount = account;
|
cfg.currentAccount = account;
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partial update: only overwrites fields present (non-null) in the given JSON, leaving all others untouched.
|
||||||
|
* Mirrors {@code SavingsAccountFacilityConfig.updateFrom(JsonObject)}. Used by the Nominee/Digital-Feature
|
||||||
|
* edit-approval flow, which only ever submits the toggles the maker actually touched.
|
||||||
|
*/
|
||||||
|
public void updateFrom(com.google.gson.JsonObject config) {
|
||||||
|
if (config == null) return;
|
||||||
|
if (config.has("atmFacility")) setAtmFacility(config.get("atmFacility").getAsBoolean());
|
||||||
|
if (config.has("atmWithdrawalFacility")) setAtmWithdrawalFacility(config.get("atmWithdrawalFacility").getAsBoolean());
|
||||||
|
if (config.has("upiFacility")) setUpiFacility(config.get("upiFacility").getAsBoolean());
|
||||||
|
if (config.has("posTransactionsFacility")) setPosTransactionsFacility(config.get("posTransactionsFacility").getAsBoolean());
|
||||||
|
if (config.has("chequeBook")) setChequeBook(config.get("chequeBook").getAsBoolean());
|
||||||
|
if (config.has("impsFacility")) setImpsFacility(config.get("impsFacility").getAsBoolean());
|
||||||
|
if (config.has("smsAlerts")) setSmsAlerts(config.get("smsAlerts").getAsBoolean());
|
||||||
|
if (config.has("bbpsFacility")) setBbpsFacility(config.get("bbpsFacility").getAsBoolean());
|
||||||
|
if (config.has("insuranceCover")) setInsuranceCover(config.get("insuranceCover").getAsBoolean());
|
||||||
|
if (config.has("bepgFacility")) setBepgFacility(config.get("bepgFacility").getAsBoolean());
|
||||||
|
if (config.has("neftFacility")) setNeftFacility(config.get("neftFacility").getAsBoolean());
|
||||||
|
if (config.has("rtgsFacility")) setRtgsFacility(config.get("rtgsFacility").getAsBoolean());
|
||||||
|
if (config.has("interBankTotalCapFacility")) setInterBankTotalCapFacility(config.get("interBankTotalCapFacility").getAsBoolean());
|
||||||
|
if (config.has("withdrawalFacility")) setWithdrawalFacility(config.get("withdrawalFacility").getAsBoolean());
|
||||||
|
if (config.has("depositFacilityKyc")) setDepositFacilityKyc(config.get("depositFacilityKyc").getAsBoolean());
|
||||||
|
if (config.has("virtualDebitCard")) setVirtualDebitCard(config.get("virtualDebitCard").getAsBoolean());
|
||||||
|
if (config.has("internetBanking")) setInternetBanking(config.get("internetBanking").getAsBoolean());
|
||||||
|
if (config.has("mobileBanking")) setMobileBanking(config.get("mobileBanking").getAsBoolean());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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.currentaccount.domain;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This repository did not previously exist - {@link CurrentAccountFacilityConfig} rows were only ever persisted via
|
||||||
|
* cascade from {@link CurrentAccountRepository} (see {@code CurrentAccount#facilityConfig}, {@code cascade = ALL}).
|
||||||
|
* Added so the Nominee/Digital-Feature edit-approval flow can save the config row directly, mirroring
|
||||||
|
* {@code SavingsAccountFacilityConfigRepository}.
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface CurrentAccountFacilityConfigRepository extends JpaRepository<CurrentAccountFacilityConfig, Long> {
|
||||||
|
|
||||||
|
Optional<CurrentAccountFacilityConfig> findByCurrentAccountId(Long currentAccountId);
|
||||||
|
}
|
||||||
+597
@@ -0,0 +1,597 @@
|
|||||||
|
/**
|
||||||
|
* 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.currentaccount.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds a pending edit request for an ACTIVE current account's Nominee + Digital & Account Features.
|
||||||
|
* Presence of a row here with {@code status = PENDING} (enforced via the unique {@link #pendingLockKey}) is itself
|
||||||
|
* the lock - nothing is applied to the real {@code m_current_account_facility_config} / {@code m_client_nominee}
|
||||||
|
* rows until a checker approves (see CurrentAccountEditWritePlatformServiceJpaRepositoryImpl). Mirrors
|
||||||
|
* {@code SavingsAccountPendingEdit} but additionally carries a full point-in-time "wide snapshot" of every business
|
||||||
|
* column on {@link CurrentAccount} at submission time, for audit/UI display purposes only (not used to drive
|
||||||
|
* approve/reject logic, which reads the live account).
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_current_account_pending_edit")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class CurrentAccountPendingEdit extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
public static final String STATUS_PENDING = "PENDING";
|
||||||
|
public static final String STATUS_APPROVED = "APPROVED";
|
||||||
|
public static final String STATUS_REJECTED = "REJECTED";
|
||||||
|
|
||||||
|
public static final String NOMINEE_OP_ADD = "ADD";
|
||||||
|
public static final String NOMINEE_OP_UPDATE = "UPDATE";
|
||||||
|
public static final String NOMINEE_OP_DELETE = "DELETE";
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "current_account_id", nullable = false)
|
||||||
|
private CurrentAccount currentAccount;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "createdby_id", nullable = false)
|
||||||
|
private AppUser maker;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "lastmodifiedby_id")
|
||||||
|
private AppUser checker;
|
||||||
|
|
||||||
|
@Column(name = "created_date", nullable = false)
|
||||||
|
private LocalDateTime createdOn;
|
||||||
|
|
||||||
|
@Column(name = "lastmodified_date")
|
||||||
|
private LocalDateTime checkedOn;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = 20)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "reject_reason", length = 500)
|
||||||
|
private String rejectReason;
|
||||||
|
|
||||||
|
/** "CURRENT_ACCOUNT_EDIT:<currentAccountId>" while PENDING, NULL otherwise - the real DB-level lock. */
|
||||||
|
@Column(name = "pending_lock_key", length = 40)
|
||||||
|
private String pendingLockKey;
|
||||||
|
|
||||||
|
/** Links to the audit-only m_portfolio_command_source row created for this edit's submit/approve/reject. */
|
||||||
|
@Column(name = "command_source_id")
|
||||||
|
private Long commandSourceId;
|
||||||
|
|
||||||
|
@Column(name = "hold_transaction_id")
|
||||||
|
private Long holdTransactionId;
|
||||||
|
|
||||||
|
@Column(name = "hold_amount", precision = 19, scale = 6)
|
||||||
|
private BigDecimal holdAmount;
|
||||||
|
|
||||||
|
// ── Digital & Account Features (same names as m_current_account_facility_config) ──
|
||||||
|
@Column(name = "facility_config_submitted", nullable = false)
|
||||||
|
private boolean facilityConfigSubmitted = false;
|
||||||
|
|
||||||
|
@Column(name = "atm_facility")
|
||||||
|
private Boolean atmFacility;
|
||||||
|
|
||||||
|
@Column(name = "atm_withdrawal_facility")
|
||||||
|
private Boolean atmWithdrawalFacility;
|
||||||
|
|
||||||
|
@Column(name = "upi_facility")
|
||||||
|
private Boolean upiFacility;
|
||||||
|
|
||||||
|
@Column(name = "pos_transactions_facility")
|
||||||
|
private Boolean posTransactionsFacility;
|
||||||
|
|
||||||
|
@Column(name = "cheque_book")
|
||||||
|
private Boolean chequeBook;
|
||||||
|
|
||||||
|
@Column(name = "imps_facility")
|
||||||
|
private Boolean impsFacility;
|
||||||
|
|
||||||
|
@Column(name = "sms_alerts")
|
||||||
|
private Boolean smsAlerts;
|
||||||
|
|
||||||
|
@Column(name = "bbps_facility")
|
||||||
|
private Boolean bbpsFacility;
|
||||||
|
|
||||||
|
@Column(name = "insurance_cover")
|
||||||
|
private Boolean insuranceCover;
|
||||||
|
|
||||||
|
@Column(name = "bepg_facility")
|
||||||
|
private Boolean bepgFacility;
|
||||||
|
|
||||||
|
@Column(name = "neft_facility")
|
||||||
|
private Boolean neftFacility;
|
||||||
|
|
||||||
|
@Column(name = "rtgs_facility")
|
||||||
|
private Boolean rtgsFacility;
|
||||||
|
|
||||||
|
@Column(name = "inter_bank_total_cap_facility")
|
||||||
|
private Boolean interBankTotalCapFacility;
|
||||||
|
|
||||||
|
@Column(name = "withdrawal_facility")
|
||||||
|
private Boolean withdrawalFacility;
|
||||||
|
|
||||||
|
@Column(name = "deposit_facility_kyc")
|
||||||
|
private Boolean depositFacilityKyc;
|
||||||
|
|
||||||
|
@Column(name = "virtual_debit_card")
|
||||||
|
private Boolean virtualDebitCard;
|
||||||
|
|
||||||
|
@Column(name = "internet_banking")
|
||||||
|
private Boolean internetBanking;
|
||||||
|
|
||||||
|
@Column(name = "mobile_banking")
|
||||||
|
private Boolean mobileBanking;
|
||||||
|
|
||||||
|
// ── Nominee change - one per request (add / update / delete) ──
|
||||||
|
@Column(name = "nominee_submitted", nullable = false)
|
||||||
|
private boolean nomineeSubmitted = false;
|
||||||
|
|
||||||
|
@Column(name = "nominee_operation", length = 10)
|
||||||
|
private String nomineeOperation;
|
||||||
|
|
||||||
|
@Column(name = "nominee_id")
|
||||||
|
private Long nomineeId;
|
||||||
|
|
||||||
|
@Column(name = "nominee_client_id")
|
||||||
|
private Long nomineeClientId;
|
||||||
|
|
||||||
|
@Column(name = "relationship_cv_id")
|
||||||
|
private Long relationshipCvId;
|
||||||
|
|
||||||
|
@Column(name = "percentage_share", precision = 5, scale = 2)
|
||||||
|
private BigDecimal percentageShare;
|
||||||
|
|
||||||
|
@Column(name = "nominee_is_active")
|
||||||
|
private Boolean nomineeIsActive;
|
||||||
|
|
||||||
|
@Column(name = "nominee_is_minor")
|
||||||
|
private Boolean nomineeIsMinor;
|
||||||
|
|
||||||
|
@Column(name = "guardian_client_id")
|
||||||
|
private Long guardianClientId;
|
||||||
|
|
||||||
|
@Column(name = "guardian_relationship_cv_id")
|
||||||
|
private Long guardianRelationshipCvId;
|
||||||
|
|
||||||
|
@Column(name = "effective_date")
|
||||||
|
private LocalDate effectiveDate;
|
||||||
|
|
||||||
|
@Column(name = "nominee_remarks", length = 500)
|
||||||
|
private String nomineeRemarks;
|
||||||
|
|
||||||
|
// ── Wide point-in-time snapshot of every business column on CurrentAccount at submission time ──
|
||||||
|
// (audit/UI display only - approve()/reject() logic never reads these, it reads the live account)
|
||||||
|
@Column(name = "account_no", length = 20)
|
||||||
|
private String snapshotAccountNo;
|
||||||
|
|
||||||
|
@Column(name = "external_id", length = 100)
|
||||||
|
private String snapshotExternalId;
|
||||||
|
|
||||||
|
@Column(name = "client_id")
|
||||||
|
private Long snapshotClientId;
|
||||||
|
|
||||||
|
@Column(name = "product_id")
|
||||||
|
private Long snapshotProductId;
|
||||||
|
|
||||||
|
@Column(name = "office_id")
|
||||||
|
private Long snapshotOfficeId;
|
||||||
|
|
||||||
|
@Column(name = "staff_id")
|
||||||
|
private Long snapshotStaffId;
|
||||||
|
|
||||||
|
@Column(name = "status_enum")
|
||||||
|
private Integer snapshotStatusEnum;
|
||||||
|
|
||||||
|
@Column(name = "sub_status_enum")
|
||||||
|
private Integer snapshotSubStatusEnum;
|
||||||
|
|
||||||
|
@Column(name = "account_type_enum")
|
||||||
|
private Integer snapshotAccountTypeEnum;
|
||||||
|
|
||||||
|
@Column(name = "submittedon_date")
|
||||||
|
private LocalDate snapshotSubmittedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "approvedon_date")
|
||||||
|
private LocalDate snapshotApprovedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "activatedon_date")
|
||||||
|
private LocalDate snapshotActivatedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "rejectedon_date")
|
||||||
|
private LocalDate snapshotRejectedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "withdrawnon_date")
|
||||||
|
private LocalDate snapshotWithdrawnOnDate;
|
||||||
|
|
||||||
|
@Column(name = "closedon_date")
|
||||||
|
private LocalDate snapshotClosedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "submittedon_userid")
|
||||||
|
private Long snapshotSubmittedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "approvedon_userid")
|
||||||
|
private Long snapshotApprovedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "activatedon_userid")
|
||||||
|
private Long snapshotActivatedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "rejectedon_userid")
|
||||||
|
private Long snapshotRejectedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "withdrawnon_userid")
|
||||||
|
private Long snapshotWithdrawnOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "closedon_userid")
|
||||||
|
private Long snapshotClosedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "currency_code", length = 3)
|
||||||
|
private String snapshotCurrencyCode;
|
||||||
|
|
||||||
|
@Column(name = "currency_digits")
|
||||||
|
private Integer snapshotCurrencyDigits;
|
||||||
|
|
||||||
|
@Column(name = "currency_multiplesof")
|
||||||
|
private Integer snapshotCurrencyMultiplesOf;
|
||||||
|
|
||||||
|
@Column(name = "nominal_annual_interest_rate", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotNominalAnnualInterestRate;
|
||||||
|
|
||||||
|
@Column(name = "interest_compounding_period_enum")
|
||||||
|
private Integer snapshotInterestCompoundingPeriodEnum;
|
||||||
|
|
||||||
|
@Column(name = "interest_posting_period_enum")
|
||||||
|
private Integer snapshotInterestPostingPeriodEnum;
|
||||||
|
|
||||||
|
@Column(name = "interest_calculation_type_enum")
|
||||||
|
private Integer snapshotInterestCalculationTypeEnum;
|
||||||
|
|
||||||
|
@Column(name = "interest_calculation_days_in_year_type_enum")
|
||||||
|
private Integer snapshotInterestCalculationDaysInYearTypeEnum;
|
||||||
|
|
||||||
|
@Column(name = "min_balance_for_interest_calculation", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMinBalanceForInterestCalculation;
|
||||||
|
|
||||||
|
@Column(name = "start_interest_calculation_date")
|
||||||
|
private LocalDate snapshotStartInterestCalculationDate;
|
||||||
|
|
||||||
|
@Column(name = "accrued_till_date")
|
||||||
|
private LocalDate snapshotAccruedTillDate;
|
||||||
|
|
||||||
|
@Column(name = "last_interest_calculation_date")
|
||||||
|
private LocalDate snapshotLastInterestCalculationDate;
|
||||||
|
|
||||||
|
@Column(name = "last_closed_business_date")
|
||||||
|
private LocalDate snapshotLastClosedBusinessDate;
|
||||||
|
|
||||||
|
@Column(name = "withhold_tax")
|
||||||
|
private Boolean snapshotWithholdTax;
|
||||||
|
|
||||||
|
@Column(name = "min_required_opening_balance", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMinRequiredOpeningBalance;
|
||||||
|
|
||||||
|
@Column(name = "lockin_period_frequency")
|
||||||
|
private Integer snapshotLockinPeriodFrequency;
|
||||||
|
|
||||||
|
@Column(name = "lockin_period_frequency_enum")
|
||||||
|
private Integer snapshotLockinPeriodFrequencyEnum;
|
||||||
|
|
||||||
|
@Column(name = "lockedin_until_date_derived")
|
||||||
|
private LocalDate snapshotLockedInUntilDate;
|
||||||
|
|
||||||
|
@Column(name = "enforce_min_required_balance")
|
||||||
|
private Boolean snapshotEnforceMinRequiredBalance;
|
||||||
|
|
||||||
|
@Column(name = "min_required_balance", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMinRequiredBalance;
|
||||||
|
|
||||||
|
@Column(name = "allow_overdraft")
|
||||||
|
private Boolean snapshotAllowOverdraft;
|
||||||
|
|
||||||
|
@Column(name = "overdraft_limit", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotOverdraftLimit;
|
||||||
|
|
||||||
|
@Column(name = "nominal_annual_interest_rate_overdraft", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotNominalAnnualInterestRateOverdraft;
|
||||||
|
|
||||||
|
@Column(name = "min_overdraft_for_interest_calculation", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMinOverdraftForInterestCalculation;
|
||||||
|
|
||||||
|
@Column(name = "is_lien_allowed")
|
||||||
|
private Boolean snapshotLienAllowed;
|
||||||
|
|
||||||
|
@Column(name = "max_allowed_lien_limit", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMaxAllowedLienLimit;
|
||||||
|
|
||||||
|
@Column(name = "on_hold_funds_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotOnHoldFundsDerived;
|
||||||
|
|
||||||
|
@Column(name = "account_balance_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotAccountBalanceDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_deposits_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalDepositsDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_withdrawals_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalWithdrawalsDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_interest_earned_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalInterestEarnedDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_interest_posted_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalInterestPostedDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_overdraft_interest_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalOverdraftInterestDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_withhold_tax_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalWithholdTaxDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_savings_amount_on_hold", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalSavingsAmountOnHold;
|
||||||
|
|
||||||
|
@Column(name = "withdrawal_fee_for_transfer")
|
||||||
|
private Boolean snapshotWithdrawalFeeForTransfer;
|
||||||
|
|
||||||
|
@Column(name = "reason_for_block", length = 500)
|
||||||
|
private String snapshotReasonForBlock;
|
||||||
|
|
||||||
|
@Column(name = "msme")
|
||||||
|
private Boolean snapshotMsme;
|
||||||
|
|
||||||
|
@Column(name = "auto_debit_facility")
|
||||||
|
private Boolean snapshotAutoDebitFacility;
|
||||||
|
|
||||||
|
@Column(name = "balance_swap_option")
|
||||||
|
private Boolean snapshotBalanceSwapOption;
|
||||||
|
|
||||||
|
@Column(name = "establishment_date")
|
||||||
|
private LocalDate snapshotEstablishmentDate;
|
||||||
|
|
||||||
|
@Column(name = "start_operation_date")
|
||||||
|
private LocalDate snapshotStartOperationDate;
|
||||||
|
|
||||||
|
@Column(name = "is_non_individual_client")
|
||||||
|
private Boolean snapshotNonIndividualClient;
|
||||||
|
|
||||||
|
@Column(name = "signatory_rights", length = 100)
|
||||||
|
private String snapshotSignatoryRights;
|
||||||
|
|
||||||
|
@Column(name = "cbs_account_type", length = 100)
|
||||||
|
private String snapshotCbsAccountType;
|
||||||
|
|
||||||
|
@Column(name = "customer_segment", length = 100)
|
||||||
|
private String snapshotCustomerSegment;
|
||||||
|
|
||||||
|
@Column(name = "account_status", length = 50)
|
||||||
|
private String snapshotAccountStatus;
|
||||||
|
|
||||||
|
@Column(name = "mode_of_operation", length = 50)
|
||||||
|
private String snapshotModeOfOperation;
|
||||||
|
|
||||||
|
@Column(name = "special_category", length = 100)
|
||||||
|
private String snapshotSpecialCategory;
|
||||||
|
|
||||||
|
@Column(name = "introducer_client_id")
|
||||||
|
private Long snapshotIntroducerClientId;
|
||||||
|
|
||||||
|
@Column(name = "introducer_name", length = 200)
|
||||||
|
private String snapshotIntroducerName;
|
||||||
|
|
||||||
|
@Column(name = "introducer_cif_number", length = 50)
|
||||||
|
private String snapshotIntroducerCifNumber;
|
||||||
|
|
||||||
|
@Column(name = "nominee_data", columnDefinition = "TEXT")
|
||||||
|
private String snapshotNomineeData;
|
||||||
|
|
||||||
|
@Column(name = "interest_calculate_selector")
|
||||||
|
private Integer snapshotInterestCalculateSelector;
|
||||||
|
|
||||||
|
@Column(name = "average_period", length = 20)
|
||||||
|
private String snapshotAveragePeriod;
|
||||||
|
|
||||||
|
@Column(name = "auto_post")
|
||||||
|
private Boolean snapshotAutoPost;
|
||||||
|
|
||||||
|
@Column(name = "frequency_fix_day")
|
||||||
|
private Integer snapshotFrequencyFixDay;
|
||||||
|
|
||||||
|
@Column(name = "enable_lockin_period")
|
||||||
|
private Boolean snapshotEnableLockinPeriod;
|
||||||
|
|
||||||
|
@Column(name = "balance_frequency", length = 50)
|
||||||
|
private String snapshotBalanceFrequency;
|
||||||
|
|
||||||
|
@Column(name = "avg_balance_calc_period", length = 10)
|
||||||
|
private String snapshotAvgBalanceCalcPeriod;
|
||||||
|
|
||||||
|
@Column(name = "account_closed_zero_balance_in_day_closing")
|
||||||
|
private Boolean snapshotAccountClosedZeroBalanceInDayClosing;
|
||||||
|
|
||||||
|
@Column(name = "valid_for_period", length = 10)
|
||||||
|
private String snapshotValidForPeriod;
|
||||||
|
|
||||||
|
@Column(name = "od_account_closed_zero_balance_in_day_closing")
|
||||||
|
private Boolean snapshotOdAccountClosedZeroBalanceInDayClosing;
|
||||||
|
|
||||||
|
@Column(name = "is_linking_enable")
|
||||||
|
private Boolean snapshotShareLinkingEnabled;
|
||||||
|
|
||||||
|
@Column(name = "terminate_true")
|
||||||
|
private Boolean snapshotTerminateTrue;
|
||||||
|
|
||||||
|
protected CurrentAccountPendingEdit() {}
|
||||||
|
|
||||||
|
public CurrentAccountPendingEdit(final CurrentAccount currentAccount, final AppUser maker) {
|
||||||
|
this.currentAccount = currentAccount;
|
||||||
|
this.maker = maker;
|
||||||
|
this.createdOn = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
this.status = STATUS_PENDING;
|
||||||
|
this.pendingLockKey = lockKeyFor(currentAccount.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String lockKeyFor(final Long currentAccountId) {
|
||||||
|
return "CURRENT_ACCOUNT_EDIT:" + currentAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a new pending-edit row and copies every business column off the live {@code account} onto this
|
||||||
|
* request's wide snapshot fields, for audit/UI display purposes only.
|
||||||
|
*/
|
||||||
|
public static CurrentAccountPendingEdit snapshotOf(final CurrentAccount account, final AppUser maker) {
|
||||||
|
final CurrentAccountPendingEdit p = new CurrentAccountPendingEdit(account, maker);
|
||||||
|
|
||||||
|
p.snapshotAccountNo = account.getAccountNo();
|
||||||
|
p.snapshotExternalId = account.getExternalId();
|
||||||
|
p.snapshotClientId = account.getClient() != null ? account.getClient().getId() : null;
|
||||||
|
p.snapshotProductId = account.getProductId();
|
||||||
|
p.snapshotOfficeId = account.getOffice() != null ? account.getOffice().getId() : null;
|
||||||
|
p.snapshotStaffId = account.getStaff() != null ? account.getStaff().getId() : null;
|
||||||
|
p.snapshotStatusEnum = account.getStatusEnum();
|
||||||
|
p.snapshotSubStatusEnum = account.getSubStatusEnum();
|
||||||
|
p.snapshotAccountTypeEnum = account.getAccountTypeEnum();
|
||||||
|
|
||||||
|
p.snapshotSubmittedOnDate = account.getSubmittedOnDate();
|
||||||
|
p.snapshotApprovedOnDate = account.getApprovedOnDate();
|
||||||
|
p.snapshotActivatedOnDate = account.getActivatedOnDate();
|
||||||
|
p.snapshotRejectedOnDate = account.getRejectedOnDate();
|
||||||
|
p.snapshotWithdrawnOnDate = account.getWithdrawnOnDate();
|
||||||
|
p.snapshotClosedOnDate = account.getClosedOnDate();
|
||||||
|
|
||||||
|
p.snapshotSubmittedOnUserId = account.getSubmittedOnByUser() != null ? account.getSubmittedOnByUser().getId() : null;
|
||||||
|
p.snapshotApprovedOnUserId = account.getApprovedOnByUser() != null ? account.getApprovedOnByUser().getId() : null;
|
||||||
|
p.snapshotActivatedOnUserId = account.getActivatedOnByUser() != null ? account.getActivatedOnByUser().getId() : null;
|
||||||
|
p.snapshotRejectedOnUserId = account.getRejectedOnByUser() != null ? account.getRejectedOnByUser().getId() : null;
|
||||||
|
p.snapshotWithdrawnOnUserId = account.getWithdrawnOnByUser() != null ? account.getWithdrawnOnByUser().getId() : null;
|
||||||
|
p.snapshotClosedOnUserId = account.getClosedOnByUser() != null ? account.getClosedOnByUser().getId() : null;
|
||||||
|
|
||||||
|
p.snapshotCurrencyCode = account.getCurrencyCode();
|
||||||
|
p.snapshotCurrencyDigits = account.getCurrencyDigits();
|
||||||
|
p.snapshotCurrencyMultiplesOf = account.getCurrencyMultiplesOf();
|
||||||
|
|
||||||
|
p.snapshotNominalAnnualInterestRate = account.getNominalAnnualInterestRate();
|
||||||
|
p.snapshotInterestCompoundingPeriodEnum = account.getInterestCompoundingPeriodEnum();
|
||||||
|
p.snapshotInterestPostingPeriodEnum = account.getInterestPostingPeriodEnum();
|
||||||
|
p.snapshotInterestCalculationTypeEnum = account.getInterestCalculationTypeEnum();
|
||||||
|
p.snapshotInterestCalculationDaysInYearTypeEnum = account.getInterestCalculationDaysInYearTypeEnum();
|
||||||
|
p.snapshotMinBalanceForInterestCalculation = account.getMinBalanceForInterestCalculation();
|
||||||
|
p.snapshotStartInterestCalculationDate = account.getStartInterestCalculationDate();
|
||||||
|
p.snapshotAccruedTillDate = account.getAccruedTillDate();
|
||||||
|
p.snapshotLastInterestCalculationDate = account.getLastInterestCalculationDate();
|
||||||
|
p.snapshotLastClosedBusinessDate = account.getLastClosedBusinessDate();
|
||||||
|
|
||||||
|
p.snapshotWithholdTax = account.isWithholdTax();
|
||||||
|
|
||||||
|
p.snapshotMinRequiredOpeningBalance = account.getMinRequiredOpeningBalance();
|
||||||
|
p.snapshotLockinPeriodFrequency = account.getLockinPeriodFrequency();
|
||||||
|
p.snapshotLockinPeriodFrequencyEnum = account.getLockinPeriodFrequencyEnum();
|
||||||
|
p.snapshotLockedInUntilDate = account.getLockedInUntilDate();
|
||||||
|
p.snapshotEnforceMinRequiredBalance = account.isEnforceMinRequiredBalance();
|
||||||
|
p.snapshotMinRequiredBalance = account.getMinRequiredBalance();
|
||||||
|
|
||||||
|
p.snapshotAllowOverdraft = account.isAllowOverdraft();
|
||||||
|
p.snapshotOverdraftLimit = account.getOverdraftLimit();
|
||||||
|
p.snapshotNominalAnnualInterestRateOverdraft = account.getNominalAnnualInterestRateOverdraft();
|
||||||
|
p.snapshotMinOverdraftForInterestCalculation = account.getMinOverdraftForInterestCalculation();
|
||||||
|
|
||||||
|
p.snapshotLienAllowed = account.isLienAllowed();
|
||||||
|
p.snapshotMaxAllowedLienLimit = account.getMaxAllowedLienLimit();
|
||||||
|
p.snapshotOnHoldFundsDerived = account.getOnHoldFundsDerived();
|
||||||
|
|
||||||
|
p.snapshotAccountBalanceDerived = account.getAccountBalanceDerived();
|
||||||
|
p.snapshotTotalDepositsDerived = account.getTotalDepositsDerived();
|
||||||
|
p.snapshotTotalWithdrawalsDerived = account.getTotalWithdrawalsDerived();
|
||||||
|
p.snapshotTotalInterestEarnedDerived = account.getTotalInterestEarnedDerived();
|
||||||
|
p.snapshotTotalInterestPostedDerived = account.getTotalInterestPostedDerived();
|
||||||
|
p.snapshotTotalOverdraftInterestDerived = account.getTotalOverdraftInterestDerived();
|
||||||
|
p.snapshotTotalWithholdTaxDerived = account.getTotalWithholdTaxDerived();
|
||||||
|
p.snapshotTotalSavingsAmountOnHold = account.getTotalSavingsAmountOnHold();
|
||||||
|
|
||||||
|
p.snapshotWithdrawalFeeForTransfer = account.isWithdrawalFeeForTransfer();
|
||||||
|
p.snapshotReasonForBlock = account.getReasonForBlock();
|
||||||
|
|
||||||
|
p.snapshotMsme = account.isMsme();
|
||||||
|
p.snapshotAutoDebitFacility = account.isAutoDebitFacility();
|
||||||
|
p.snapshotBalanceSwapOption = account.isBalanceSwapOption();
|
||||||
|
p.snapshotEstablishmentDate = account.getEstablishmentDate();
|
||||||
|
p.snapshotStartOperationDate = account.getStartOperationDate();
|
||||||
|
p.snapshotNonIndividualClient = account.isNonIndividualClient();
|
||||||
|
p.snapshotSignatoryRights = account.getSignatoryRights();
|
||||||
|
|
||||||
|
p.snapshotCbsAccountType = account.getCbsAccountType();
|
||||||
|
p.snapshotCustomerSegment = account.getCustomerSegment();
|
||||||
|
p.snapshotAccountStatus = account.getAccountStatus();
|
||||||
|
p.snapshotModeOfOperation = account.getModeOfOperation();
|
||||||
|
p.snapshotSpecialCategory = account.getSpecialCategory();
|
||||||
|
|
||||||
|
p.snapshotIntroducerClientId = account.getIntroducerClientId();
|
||||||
|
p.snapshotIntroducerName = account.getIntroducerName();
|
||||||
|
p.snapshotIntroducerCifNumber = account.getIntroducerCifNumber();
|
||||||
|
|
||||||
|
p.snapshotNomineeData = account.getNomineeData();
|
||||||
|
|
||||||
|
p.snapshotInterestCalculateSelector = account.getInterestCalculateSelector();
|
||||||
|
p.snapshotAveragePeriod = account.getAveragePeriod();
|
||||||
|
p.snapshotAutoPost = account.isAutoPost();
|
||||||
|
p.snapshotFrequencyFixDay = account.getFrequencyFixDay();
|
||||||
|
p.snapshotEnableLockinPeriod = account.isEnableLockinPeriod();
|
||||||
|
p.snapshotBalanceFrequency = account.getBalanceFrequency();
|
||||||
|
p.snapshotAvgBalanceCalcPeriod = account.getAvgBalanceCalcPeriod();
|
||||||
|
|
||||||
|
p.snapshotAccountClosedZeroBalanceInDayClosing = account.getAccountClosedZeroBalanceInDayClosing();
|
||||||
|
p.snapshotValidForPeriod = account.getValidForPeriod();
|
||||||
|
p.snapshotOdAccountClosedZeroBalanceInDayClosing = account.isOdAccountClosedZeroBalanceInDayClosing();
|
||||||
|
|
||||||
|
p.snapshotShareLinkingEnabled = account.isShareLinkingEnabled();
|
||||||
|
p.snapshotTerminateTrue = account.isTerminateTrue();
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPending() {
|
||||||
|
return STATUS_PENDING.equals(this.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markApproved(final AppUser checker) {
|
||||||
|
this.status = STATUS_APPROVED;
|
||||||
|
this.checker = checker;
|
||||||
|
this.checkedOn = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
this.pendingLockKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markRejected(final AppUser checker, final String reason) {
|
||||||
|
this.status = STATUS_REJECTED;
|
||||||
|
this.checker = checker;
|
||||||
|
this.checkedOn = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
this.rejectReason = reason;
|
||||||
|
this.pendingLockKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -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.currentaccount.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CurrentAccountPendingEditRepository extends JpaRepository<CurrentAccountPendingEdit, Long> {
|
||||||
|
|
||||||
|
Optional<CurrentAccountPendingEdit> findByCurrentAccountIdAndStatus(Long currentAccountId, String status);
|
||||||
|
|
||||||
|
List<CurrentAccountPendingEdit> findByStatus(String status);
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.currentaccount.domain;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Wrapper for {@link CurrentAccountRepository} that is responsible for checking if {@link CurrentAccount} is
|
||||||
|
* returned when using <code>findOne</code> repository method and throwing an appropriate not found exception.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This is to avoid need for checking and throwing in multiple areas of code base where
|
||||||
|
* {@link CurrentAccountRepository} is required.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CurrentAccountRepositoryWrapper {
|
||||||
|
|
||||||
|
private final CurrentAccountRepository repository;
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public CurrentAccount findOneWithNotFoundDetection(final Long id) {
|
||||||
|
return this.repository.findById(id).orElseThrow(() -> new CurrentAccountNotFoundException(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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.currentaccount.exception;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown at edit-submission time when the account's available balance cannot cover the charge for
|
||||||
|
* the digital/account feature(s) the maker is requesting to enable.
|
||||||
|
*/
|
||||||
|
public class CurrentAccountEditInsufficientBalanceException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentAccountEditInsufficientBalanceException(final Long currentAccountId, final BigDecimal availableBalance,
|
||||||
|
final BigDecimal requiredAmount) {
|
||||||
|
super("error.msg.currentaccount.edit.insufficient.balance",
|
||||||
|
"Account balance is insufficient for the requested feature(s). Available balance: " + availableBalance
|
||||||
|
+ ", required: " + requiredAmount,
|
||||||
|
currentAccountId, availableBalance, requiredAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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.currentaccount.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentAccountEditPendingApprovalException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentAccountEditPendingApprovalException(final Long currentAccountId) {
|
||||||
|
super("error.msg.currentaccount.edit.pending.approval.exists",
|
||||||
|
"Current account with identifier " + currentAccountId + " already has a Nominee/Digital-Feature edit pending approval",
|
||||||
|
currentAccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -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.currentaccount.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentAccountEditSelfApprovalNotAllowedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentAccountEditSelfApprovalNotAllowedException(final Long currentAccountId) {
|
||||||
|
super("error.msg.currentaccount.edit.self.approval.not.allowed",
|
||||||
|
"The maker of the pending Nominee/Digital-Feature edit for current account " + currentAccountId
|
||||||
|
+ " cannot also approve or reject it",
|
||||||
|
currentAccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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.currentaccount.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentAccountNoPendingEditException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentAccountNoPendingEditException(final Long currentAccountId) {
|
||||||
|
super("error.msg.currentaccount.no.pending.edit",
|
||||||
|
"Current account with identifier " + currentAccountId + " has no Nominee/Digital-Feature edit awaiting approval",
|
||||||
|
currentAccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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.currentaccount.service;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.portfolio.currentaccount.data.CurrentAccountPendingEditData;
|
||||||
|
|
||||||
|
public interface CurrentAccountEditWritePlatformService {
|
||||||
|
|
||||||
|
CommandProcessingResult submitEditForApproval(Long currentAccountId, JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult approveEdit(Long currentAccountId);
|
||||||
|
|
||||||
|
CommandProcessingResult rejectEdit(Long currentAccountId, String rejectReason);
|
||||||
|
|
||||||
|
CurrentAccountPendingEditData retrievePendingEdit(Long currentAccountId);
|
||||||
|
|
||||||
|
Collection<CurrentAccountPendingEditData> retrieveAllPendingEdits();
|
||||||
|
}
|
||||||
+33
-10
@@ -37,6 +37,33 @@ public class CurrentAccountNumberGenerator {
|
|||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
private String getNextSequence() {
|
||||||
|
final java.util.List<String> accountNos = this.jdbcTemplate.query(
|
||||||
|
"SELECT account_no FROM m_current_account WHERE account_no IS NOT NULL",
|
||||||
|
(rs, rowNum) -> rs.getString(1));
|
||||||
|
|
||||||
|
long maxVal = 0;
|
||||||
|
for (String accNo : accountNos) {
|
||||||
|
if (accNo != null && accNo.length() >= 5) {
|
||||||
|
String suffix = accNo.substring(accNo.length() - 5);
|
||||||
|
try {
|
||||||
|
long val = Long.parseLong(suffix);
|
||||||
|
if (val > maxVal) {
|
||||||
|
maxVal = val;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// non-numeric suffix - skip when computing the max existing sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String nextSeq = StringUtils.leftPad(String.valueOf(maxVal + 1), 5, '0');
|
||||||
|
if (nextSeq.length() > 5) {
|
||||||
|
nextSeq = nextSeq.substring(nextSeq.length() - 5);
|
||||||
|
}
|
||||||
|
return nextSeq;
|
||||||
|
}
|
||||||
|
|
||||||
public String generate(CurrentAccount account) {
|
public String generate(CurrentAccount account) {
|
||||||
Long productId = account.getProductId();
|
Long productId = account.getProductId();
|
||||||
|
|
||||||
@@ -59,11 +86,10 @@ public class CurrentAccountNumberGenerator {
|
|||||||
String officeId = StringUtils.leftPad(account.getOffice().getId().toString(), 2, '0');
|
String officeId = StringUtils.leftPad(account.getOffice().getId().toString(), 2, '0');
|
||||||
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
|
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
|
||||||
|
|
||||||
// Account ID — 5 digits
|
// Next sequence from existing account numbers
|
||||||
String accountId = StringUtils.leftPad(account.getId().toString(), 5, '0');
|
String nextSeq = getNextSequence();
|
||||||
if (accountId.length() > 5) accountId = accountId.substring(accountId.length() - 5);
|
|
||||||
|
|
||||||
return CBS_PREFIX + glCode + officeId + accountId;
|
return CBS_PREFIX + glCode + officeId + nextSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String preview(Long productId, Long officeId) {
|
public String preview(Long productId, Long officeId) {
|
||||||
@@ -80,13 +106,10 @@ public class CurrentAccountNumberGenerator {
|
|||||||
String officeStr = StringUtils.leftPad(officeId != null ? officeId.toString() : "0", 2, '0');
|
String officeStr = StringUtils.leftPad(officeId != null ? officeId.toString() : "0", 2, '0');
|
||||||
if (officeStr.length() > 2) officeStr = officeStr.substring(officeStr.length() - 2);
|
if (officeStr.length() > 2) officeStr = officeStr.substring(officeStr.length() - 2);
|
||||||
|
|
||||||
// Next account ID estimate from m_current_account
|
// Next sequence from existing account numbers
|
||||||
Long maxId = jdbcTemplate.query(
|
String nextSeq = getNextSequence();
|
||||||
"SELECT MAX(id) FROM m_current_account",
|
|
||||||
rs -> rs.next() ? rs.getLong(1) : 0L);
|
|
||||||
String nextSeq = StringUtils.leftPad(String.valueOf((maxId != null ? maxId : 0L) + 1), 5, '0');
|
|
||||||
if (nextSeq.length() > 5) nextSeq = nextSeq.substring(nextSeq.length() - 5);
|
|
||||||
|
|
||||||
return CBS_PREFIX + glCode + officeStr + nextSeq;
|
return CBS_PREFIX + glCode + officeStr + nextSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -26,6 +26,8 @@ public interface CurrentAccountReadPlatformService {
|
|||||||
|
|
||||||
Map<String, Object> retrieveOne(Long accountId);
|
Map<String, Object> retrieveOne(Long accountId);
|
||||||
|
|
||||||
|
Map<String, Object> retrieveOneTransaction(Long accountId, Long transactionId);
|
||||||
|
|
||||||
List<Map<String, Object>> retrieveAll();
|
List<Map<String, Object>> retrieveAll();
|
||||||
|
|
||||||
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName);
|
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName);
|
||||||
|
|||||||
+12
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.currentaccount.service;
|
package org.apache.fineract.portfolio.currentaccount.service;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
|
||||||
@@ -40,4 +41,15 @@ public interface CurrentAccountWritePlatformService {
|
|||||||
CommandProcessingResult modifyApplication(Long accountId, JsonCommand command);
|
CommandProcessingResult modifyApplication(Long accountId, JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult deleteApplication(Long accountId);
|
CommandProcessingResult deleteApplication(Long accountId);
|
||||||
|
|
||||||
|
CommandProcessingResult terminateAccount(Long accountId, JsonCommand command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves each of the given (newly-enabled) facility types to its mapped charge via
|
||||||
|
* {@code m_facility_charge_mapping}, creates a {@code CurrentAccountCharge} for it (skipping any facility type
|
||||||
|
* for which the account already has a paid, non-waived charge for the same mapped {@code Charge}), collects it
|
||||||
|
* from the account's running balance as of today, and posts the corresponding GL journal entries. Reuses the
|
||||||
|
* same private charge-creation-and-collection helper that {@code activate()} uses for activation-time charges.
|
||||||
|
*/
|
||||||
|
void applyAndCollectFacilityCharges(Long currentAccountId, Set<String> newlyEnabledFacilityTypes);
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -54,7 +54,6 @@ import com.google.gson.JsonElement;
|
|||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.MonthDay;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -76,7 +75,6 @@ import org.apache.fineract.portfolio.savings.DepositAccountType;
|
|||||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
|
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
|
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
|
||||||
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
|
||||||
import org.apache.fineract.portfolio.savings.data.SavingsProductAccountingDataValidator;
|
import org.apache.fineract.portfolio.savings.data.SavingsProductAccountingDataValidator;
|
||||||
|
|||||||
-7
@@ -56,18 +56,11 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountT
|
|||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productCodeParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productCodeParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.glAccountIdParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.glAccountIdParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveDateParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveToParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.endDateParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerRequiredParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerRequiredParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shareLinkingEnabledParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shareLinkingEnabledParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dormancyAlertParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dormancyAlertParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculateSelectorParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.averagePeriodParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoPostParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoPostParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.frequencyFixDayParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lastInterestDateParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enableLockinPeriodParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enableLockinPeriodParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.balanceSwapOptionParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.balanceSwapOptionParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.msmeParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.msmeParamName;
|
||||||
|
|||||||
+2
-1
@@ -26,6 +26,7 @@ import jakarta.persistence.OneToOne;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_current_product_deposit_limit_kyc")
|
@Table(name = "m_current_product_deposit_limit_kyc")
|
||||||
@@ -57,12 +58,12 @@ public class CurrentProductDepositLimitKyc extends AbstractPersistableCustom<Lon
|
|||||||
private LocalDateTime createdDate;
|
private LocalDateTime createdDate;
|
||||||
|
|
||||||
protected CurrentProductDepositLimitKyc() {
|
protected CurrentProductDepositLimitKyc() {
|
||||||
this.createdDate = LocalDateTime.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentProductDepositLimitKyc(CurrentProduct currentProduct) {
|
public CurrentProductDepositLimitKyc(CurrentProduct currentProduct) {
|
||||||
this();
|
this();
|
||||||
this.currentProduct = currentProduct;
|
this.currentProduct = currentProduct;
|
||||||
|
this.createdDate = DateUtils.getLocalDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentProduct getCurrentProduct() { return currentProduct; }
|
public CurrentProduct getCurrentProduct() { return currentProduct; }
|
||||||
|
|||||||
+2
-1
@@ -26,6 +26,7 @@ import jakarta.persistence.OneToOne;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_current_product_facility_config")
|
@Table(name = "m_current_product_facility_config")
|
||||||
@@ -87,12 +88,12 @@ public class CurrentProductFacilityConfig extends AbstractPersistableCustom<Long
|
|||||||
private LocalDateTime createdDate;
|
private LocalDateTime createdDate;
|
||||||
|
|
||||||
protected CurrentProductFacilityConfig() {
|
protected CurrentProductFacilityConfig() {
|
||||||
this.createdDate = LocalDateTime.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentProductFacilityConfig(CurrentProduct currentProduct) {
|
public CurrentProductFacilityConfig(CurrentProduct currentProduct) {
|
||||||
this();
|
this();
|
||||||
this.currentProduct = currentProduct;
|
this.currentProduct = currentProduct;
|
||||||
|
this.createdDate = DateUtils.getLocalDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentProduct getCurrentProduct() { return currentProduct; }
|
public CurrentProduct getCurrentProduct() { return currentProduct; }
|
||||||
|
|||||||
+2
-1
@@ -26,6 +26,7 @@ import jakarta.persistence.ManyToOne;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_current_product_facility_limits")
|
@Table(name = "m_current_product_facility_limits")
|
||||||
@@ -51,7 +52,6 @@ public class CurrentProductFacilityLimit extends AbstractPersistableCustom<Long>
|
|||||||
private LocalDateTime createdDate;
|
private LocalDateTime createdDate;
|
||||||
|
|
||||||
protected CurrentProductFacilityLimit() {
|
protected CurrentProductFacilityLimit() {
|
||||||
this.createdDate = LocalDateTime.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CurrentProductFacilityLimit(CurrentProduct currentProduct, String limitType,
|
private CurrentProductFacilityLimit(CurrentProduct currentProduct, String limitType,
|
||||||
@@ -62,6 +62,7 @@ public class CurrentProductFacilityLimit extends AbstractPersistableCustom<Long>
|
|||||||
this.generalLimit = generalLimit;
|
this.generalLimit = generalLimit;
|
||||||
this.minorAccountLimit = minorAccountLimit;
|
this.minorAccountLimit = minorAccountLimit;
|
||||||
this.smallSavingLimit = smallSavingLimit;
|
this.smallSavingLimit = smallSavingLimit;
|
||||||
|
this.createdDate = DateUtils.getLocalDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CurrentProductFacilityLimit create(CurrentProduct currentProduct, String limitType,
|
public static CurrentProductFacilityLimit create(CurrentProduct currentProduct, String limitType,
|
||||||
|
|||||||
+29
@@ -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.currentproduct.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentProductEditPendingApprovalException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentProductEditPendingApprovalException(final Long productId) {
|
||||||
|
super("error.msg.currentproduct.edit.pending.approval",
|
||||||
|
"Current product with identifier " + productId + " already has an edit pending approval", productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -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.currentproduct.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentProductNoPendingEditException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentProductNoPendingEditException(final Long productId) {
|
||||||
|
super("error.msg.currentproduct.no.pending.edit",
|
||||||
|
"Current product with identifier " + productId + " has no edit pending approval", productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -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.currentproduct.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentProductSelfApprovalNotAllowedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentProductSelfApprovalNotAllowedException(final Long productId) {
|
||||||
|
super("error.msg.currentproduct.self.approval.not.allowed",
|
||||||
|
"The maker of the pending edit for current product " + productId + " cannot also approve or reject it", productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.currentproduct.service;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.SavingsProductTempData;
|
||||||
|
|
||||||
public interface CurrentProductReadPlatformService {
|
public interface CurrentProductReadPlatformService {
|
||||||
|
|
||||||
@@ -28,4 +29,8 @@ public interface CurrentProductReadPlatformService {
|
|||||||
Collection<SavingsProductData> retrieveAllForLookup();
|
Collection<SavingsProductData> retrieveAllForLookup();
|
||||||
|
|
||||||
SavingsProductData retrieveOne(Long productId);
|
SavingsProductData retrieveOne(Long productId);
|
||||||
|
|
||||||
|
Collection<SavingsProductTempData> retrieveAllPendingEdits();
|
||||||
|
|
||||||
|
SavingsProductTempData retrievePendingEdit(Long productId);
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-3
@@ -19,13 +19,11 @@
|
|||||||
package org.apache.fineract.portfolio.currentproduct.service;
|
package org.apache.fineract.portfolio.currentproduct.service;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -42,13 +40,23 @@ import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilit
|
|||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
||||||
|
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductNoPendingEditException;
|
||||||
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.SavingsProductTempData;
|
||||||
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
||||||
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
|
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
|
||||||
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
import org.springframework.dao.EmptyResultDataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEdit;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedFieldRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditChargeRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMappingRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimitRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFieldMapper;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class CurrentProductReadPlatformServiceImpl implements CurrentProductReadPlatformService {
|
public class CurrentProductReadPlatformServiceImpl implements CurrentProductReadPlatformService {
|
||||||
@@ -58,6 +66,11 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
|||||||
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
||||||
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
||||||
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
||||||
|
private final ProductPendingEditRepository productPendingEditRepository;
|
||||||
|
private final ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository;
|
||||||
|
private final ProductPendingEditChargeRepository pendingEditChargeRepository;
|
||||||
|
private final ProductPendingEditGlMappingRepository pendingEditGlMappingRepository;
|
||||||
|
private final ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository;
|
||||||
|
|
||||||
private final CurrentProductMapper currentProductRowMapper = new CurrentProductMapper();
|
private final CurrentProductMapper currentProductRowMapper = new CurrentProductMapper();
|
||||||
private final CurrentProductLookupMapper currentProductLookupsRowMapper = new CurrentProductLookupMapper();
|
private final CurrentProductLookupMapper currentProductLookupsRowMapper = new CurrentProductLookupMapper();
|
||||||
@@ -147,6 +160,64 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final String PENDING_EDIT_SCHEMA = "spt.id as tempId, spt.product_id as savingsProductId, "
|
||||||
|
+ "cp.name as savingsProductName, spt.createdby_id as makerId, maker.username as makerUsername, "
|
||||||
|
+ "spt.lastmodifiedby_id as checkerId, checker.username as checkerUsername, spt.created_date as createdOn, "
|
||||||
|
+ "spt.lastmodified_date as checkedOn, spt.status as status, spt.reject_reason as rejectReason "
|
||||||
|
+ "from m_saving_product_temp spt "
|
||||||
|
+ "join m_current_product cp on cp.id = spt.product_id "
|
||||||
|
+ "join m_appuser maker on maker.id = spt.createdby_id "
|
||||||
|
+ "left join m_appuser checker on checker.id = spt.lastmodifiedby_id "
|
||||||
|
+ "where spt.product_type = 'CURRENT' ";
|
||||||
|
|
||||||
|
private RowMapper<SavingsProductTempData> getPendingEditRowMapper() {
|
||||||
|
return (rs, rowNum) -> {
|
||||||
|
final SavingsProductTempData data = new SavingsProductTempData();
|
||||||
|
data.setId(rs.getLong("tempId"));
|
||||||
|
data.setSavingsProductId(rs.getLong("savingsProductId"));
|
||||||
|
data.setSavingsProductName(rs.getString("savingsProductName"));
|
||||||
|
data.setMakerId(rs.getLong("makerId"));
|
||||||
|
data.setMakerUsername(rs.getString("makerUsername"));
|
||||||
|
data.setCheckerId(JdbcSupport.getLong(rs, "checkerId"));
|
||||||
|
data.setCheckerUsername(rs.getString("checkerUsername"));
|
||||||
|
data.setCreatedOn(rs.getTimestamp("createdOn") == null ? null : rs.getTimestamp("createdOn").toLocalDateTime());
|
||||||
|
data.setCheckedOn(rs.getTimestamp("checkedOn") == null ? null : rs.getTimestamp("checkedOn").toLocalDateTime());
|
||||||
|
data.setStatus(rs.getString("status"));
|
||||||
|
data.setRejectReason(rs.getString("rejectReason"));
|
||||||
|
|
||||||
|
final Long tempId = data.getId();
|
||||||
|
final java.util.Optional<ProductPendingEdit> opt = this.productPendingEditRepository.findById(tempId);
|
||||||
|
if (opt.isPresent()) {
|
||||||
|
final ProductPendingEdit pendingEdit = opt.get();
|
||||||
|
final Set<String> submittedFields = new java.util.HashSet<>(
|
||||||
|
this.pendingEditSubmittedFieldRepository.findFieldNamesByPendingEditId(pendingEdit.getId()));
|
||||||
|
final Map<String, Object> proposedChanges = ProductPendingEditFieldMapper.reconstructProposedChangesMap(
|
||||||
|
pendingEdit, submittedFields, this.pendingEditChargeRepository, this.pendingEditGlMappingRepository,
|
||||||
|
this.pendingEditFacilityLimitRepository, GSON);
|
||||||
|
data.setProposedChanges(proposedChanges);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<SavingsProductTempData> retrieveAllPendingEdits() {
|
||||||
|
this.context.authenticatedUser();
|
||||||
|
final String sql = "select " + PENDING_EDIT_SCHEMA + "and spt.status = 'PENDING' order by spt.created_date desc";
|
||||||
|
return this.jdbcTemplate.query(sql, getPendingEditRowMapper());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SavingsProductTempData retrievePendingEdit(final Long productId) {
|
||||||
|
this.context.authenticatedUser();
|
||||||
|
final String sql = "select " + PENDING_EDIT_SCHEMA + "and spt.status = 'PENDING' and spt.product_id = ?";
|
||||||
|
try {
|
||||||
|
return this.jdbcTemplate.queryForObject(sql, getPendingEditRowMapper(), new Object[] { productId }); // NOSONAR
|
||||||
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
|
throw new CurrentProductNoPendingEditException(productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class CurrentProductMapper implements RowMapper<SavingsProductData> {
|
private static final class CurrentProductMapper implements RowMapper<SavingsProductData> {
|
||||||
|
|
||||||
private final String schemaSql;
|
private final String schemaSql;
|
||||||
@@ -197,7 +268,9 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
|||||||
sqlBuilder.append("cp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
sqlBuilder.append("cp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||||
sqlBuilder.append("cp.balance_swap_option as balanceSwapOption, cp.msme as msme, cp.auto_debit_facility as autoDebitFacility, ");
|
sqlBuilder.append("cp.balance_swap_option as balanceSwapOption, cp.msme as msme, cp.auto_debit_facility as autoDebitFacility, ");
|
||||||
sqlBuilder.append("cp.avg_balance_calc_period as avgBalanceCalcPeriod, cp.valid_for_period as validForPeriod, ");
|
sqlBuilder.append("cp.avg_balance_calc_period as avgBalanceCalcPeriod, cp.valid_for_period as validForPeriod, ");
|
||||||
sqlBuilder.append("cp.od_account_closed_zero_balance_in_day_closing as odAccountClosedZeroBalanceInDayClosing ");
|
sqlBuilder.append("cp.od_account_closed_zero_balance_in_day_closing as odAccountClosedZeroBalanceInDayClosing, ");
|
||||||
|
sqlBuilder.append("exists(select 1 from m_saving_product_temp spt where spt.product_id = cp.id "
|
||||||
|
+ "and spt.product_type = 'CURRENT' and spt.status = 'PENDING') as isPendingApproval ");
|
||||||
|
|
||||||
sqlBuilder.append("from m_current_product cp ");
|
sqlBuilder.append("from m_current_product cp ");
|
||||||
sqlBuilder.append("join m_currency curr on curr.code = cp.currency_code ");
|
sqlBuilder.append("join m_currency curr on curr.code = cp.currency_code ");
|
||||||
@@ -321,6 +394,7 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
|||||||
productData.setAvgBalanceCalcPeriod(rs.getString("avgBalanceCalcPeriod"));
|
productData.setAvgBalanceCalcPeriod(rs.getString("avgBalanceCalcPeriod"));
|
||||||
productData.setValidForPeriod(rs.getString("validForPeriod"));
|
productData.setValidForPeriod(rs.getString("validForPeriod"));
|
||||||
productData.setOdAccountClosedZeroBalanceInDayClosing(rs.getBoolean("odAccountClosedZeroBalanceInDayClosing"));
|
productData.setOdAccountClosedZeroBalanceInDayClosing(rs.getBoolean("odAccountClosedZeroBalanceInDayClosing"));
|
||||||
|
productData.setIsPendingApproval(rs.getBoolean("isPendingApproval"));
|
||||||
|
|
||||||
return productData;
|
return productData;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
@@ -28,4 +28,21 @@ public interface CurrentProductWritePlatformService {
|
|||||||
CommandProcessingResult update(Long productId, JsonCommand command);
|
CommandProcessingResult update(Long productId, JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult delete(Long productId);
|
CommandProcessingResult delete(Long productId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits an edit for approval instead of applying it directly. Fails if a pending edit already exists for this
|
||||||
|
* product (that pending row is itself the lock).
|
||||||
|
*/
|
||||||
|
CommandProcessingResult submitEditForApproval(Long productId, JsonCommand command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replays the stored pending edit through the normal update path and marks it approved. Fails if the approving
|
||||||
|
* user is the same user who submitted the edit.
|
||||||
|
*/
|
||||||
|
CommandProcessingResult approveEdit(Long productId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discards the pending edit. Fails if the rejecting user is the same user who submitted the edit.
|
||||||
|
*/
|
||||||
|
CommandProcessingResult rejectEdit(Long productId, String rejectReason);
|
||||||
}
|
}
|
||||||
|
|||||||
+245
@@ -31,6 +31,9 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
||||||
|
import org.apache.fineract.commands.domain.CommandProcessingResultType;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSource;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
@@ -38,6 +41,8 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
|
|||||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.charge.domain.Charge;
|
import org.apache.fineract.portfolio.charge.domain.Charge;
|
||||||
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
||||||
@@ -50,10 +55,26 @@ import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilit
|
|||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
|
||||||
|
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductEditPendingApprovalException;
|
||||||
|
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductNoPendingEditException;
|
||||||
|
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductSelfApprovalNotAllowedException;
|
||||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEdit;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditRepository;
|
||||||
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedField;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedFieldRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditCharge;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditChargeRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimit;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimitRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMapping;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMappingRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFieldMapper;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
|
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -61,6 +82,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements CurrentProductWritePlatformService {
|
public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements CurrentProductWritePlatformService {
|
||||||
|
|
||||||
|
private static final com.google.gson.Gson GSON = new com.google.gson.Gson();
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final CurrentProductRepository currentProductRepository;
|
private final CurrentProductRepository currentProductRepository;
|
||||||
private final CurrentProductDataValidator fromApiJsonDataValidator;
|
private final CurrentProductDataValidator fromApiJsonDataValidator;
|
||||||
@@ -69,6 +91,13 @@ public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements Curr
|
|||||||
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
||||||
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
||||||
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
||||||
|
private final ProductPendingEditRepository productPendingEditRepository;
|
||||||
|
private final ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository;
|
||||||
|
private final ProductPendingEditChargeRepository pendingEditChargeRepository;
|
||||||
|
private final ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository;
|
||||||
|
private final ProductPendingEditGlMappingRepository pendingEditGlMappingRepository;
|
||||||
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
|
private final CommandSourceRepository commandSourceRepository;
|
||||||
|
|
||||||
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dae) {
|
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dae) {
|
||||||
String msgCode = "error.msg." + DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME;
|
String msgCode = "error.msg." + DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME;
|
||||||
@@ -251,6 +280,222 @@ public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements Curr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult submitEditForApproval(final Long productId, final JsonCommand command) {
|
||||||
|
|
||||||
|
final AppUser maker = this.context.authenticatedUser();
|
||||||
|
final CurrentProduct product = this.currentProductRepository.findById(productId)
|
||||||
|
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
|
||||||
|
|
||||||
|
if (this.productPendingEditRepository
|
||||||
|
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_CURRENT, productId, ProductPendingEdit.STATUS_PENDING)
|
||||||
|
.isPresent()) {
|
||||||
|
throw new CurrentProductEditPendingApprovalException(productId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fromApiJsonDataValidator.validateForUpdate(command.json(), product);
|
||||||
|
|
||||||
|
final ProductPendingEdit pendingEdit = new ProductPendingEdit(ProductPendingEdit.TYPE_CURRENT, productId, maker);
|
||||||
|
|
||||||
|
final java.util.Set<String> submitted = ProductPendingEditFieldMapper.populateCoreFields(command, pendingEdit.getCoreFields());
|
||||||
|
final java.util.Set<String> currentOnlySubmitted = ProductPendingEditFieldMapper.populateCurrentOnlyFields(command, pendingEdit.getCurrentOnlyFields());
|
||||||
|
final boolean facilityConfigSubmitted = ProductPendingEditFieldMapper.populateFacilityConfig(command, pendingEdit.getFacilityConfigFields());
|
||||||
|
final boolean depositLimitKycSubmitted = ProductPendingEditFieldMapper.populateDepositLimitKyc(command, pendingEdit.getDepositLimitKycFields());
|
||||||
|
final boolean glMappingSubmitted = ProductPendingEditFieldMapper.populateGlAccountMapping(command, pendingEdit.getGlAccountMappingFields());
|
||||||
|
|
||||||
|
if (!currentOnlySubmitted.isEmpty()) {
|
||||||
|
submitted.add("avgBalanceCalcPeriod");
|
||||||
|
submitted.add("validForPeriod");
|
||||||
|
submitted.add("odAccountClosedZeroBalanceInDayClosing");
|
||||||
|
}
|
||||||
|
if (facilityConfigSubmitted) {
|
||||||
|
submitted.add("accountFacilityConfig");
|
||||||
|
}
|
||||||
|
if (depositLimitKycSubmitted) {
|
||||||
|
submitted.add("depositLimitKyc");
|
||||||
|
}
|
||||||
|
if (glMappingSubmitted) {
|
||||||
|
submitted.add("glAccountMapping");
|
||||||
|
}
|
||||||
|
|
||||||
|
final CommandSource auditEntry = CommandSource.builder() //
|
||||||
|
.actionName("UPDATE") //
|
||||||
|
.entityName("CURRENTPRODUCT") //
|
||||||
|
.resourceGetUrl("/currentproducts/" + productId) //
|
||||||
|
.resourceId(productId) //
|
||||||
|
.commandAsJson(command.json()) //
|
||||||
|
.maker(maker) //
|
||||||
|
.madeOnDate(DateUtils.getAuditOffsetDateTime()) //
|
||||||
|
.status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()) //
|
||||||
|
.idempotencyKey(java.util.UUID.randomUUID().toString()) //
|
||||||
|
.sanitized(false) //
|
||||||
|
.build();
|
||||||
|
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||||
|
pendingEdit.setCommandSourceId(auditEntry.getId());
|
||||||
|
|
||||||
|
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||||
|
|
||||||
|
for (final String fieldName : submitted) {
|
||||||
|
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Long> chargeIds = ProductPendingEditFieldMapper.extractChargeIds(command);
|
||||||
|
if (chargeIds != null) {
|
||||||
|
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, "charges"));
|
||||||
|
for (final Long chargeId : chargeIds) {
|
||||||
|
this.pendingEditChargeRepository.save(new ProductPendingEditCharge(pendingEdit, chargeId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<ProductPendingEditFacilityLimit> facilityLimits = ProductPendingEditFieldMapper.extractFacilityLimits(command, pendingEdit);
|
||||||
|
if (facilityLimits != null) {
|
||||||
|
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, "accountFacilityLimits"));
|
||||||
|
this.pendingEditFacilityLimitRepository.saveAll(facilityLimits);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<ProductPendingEditGlMapping> glMappings = ProductPendingEditFieldMapper.extractGlArrayMappings(command, pendingEdit);
|
||||||
|
if (glMappings != null) {
|
||||||
|
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, "glArrayMappings"));
|
||||||
|
this.pendingEditGlMappingRepository.saveAll(glMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder() //
|
||||||
|
.withEntityId(product.getId()) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult approveEdit(final Long productId) {
|
||||||
|
|
||||||
|
final AppUser checker = this.context.authenticatedUser();
|
||||||
|
final ProductPendingEdit pendingEdit = this.productPendingEditRepository
|
||||||
|
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_CURRENT, productId, ProductPendingEdit.STATUS_PENDING)
|
||||||
|
.orElseThrow(() -> new CurrentProductNoPendingEditException(productId));
|
||||||
|
|
||||||
|
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||||
|
throw new CurrentProductSelfApprovalNotAllowedException(productId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> submittedFields = new java.util.HashSet<>(
|
||||||
|
this.pendingEditSubmittedFieldRepository.findFieldNamesByPendingEditId(pendingEdit.getId()));
|
||||||
|
final JsonObject root = new JsonObject();
|
||||||
|
root.addProperty("locale", "en");
|
||||||
|
root.addProperty("dateFormat", "yyyy-MM-dd");
|
||||||
|
ProductPendingEditFieldMapper.writeCoreFieldsToJson(root, pendingEdit.getCoreFields(), submittedFields);
|
||||||
|
ProductPendingEditFieldMapper.writeCurrentOnlyFieldsToJson(root, pendingEdit.getCurrentOnlyFields(), submittedFields);
|
||||||
|
ProductPendingEditFieldMapper.writeFacilityConfigToJson(root, pendingEdit.getFacilityConfigFields(),
|
||||||
|
submittedFields.contains("accountFacilityConfig"));
|
||||||
|
ProductPendingEditFieldMapper.writeDepositLimitKycToJson(root, pendingEdit.getDepositLimitKycFields(),
|
||||||
|
submittedFields.contains("depositLimitKyc"));
|
||||||
|
if (submittedFields.contains("glAccountMapping")) {
|
||||||
|
ProductPendingEditFieldMapper.writeGlAccountMappingToJson(root, pendingEdit.getGlAccountMappingFields());
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("charges")) {
|
||||||
|
ProductPendingEditFieldMapper.writeChargesToJson(root,
|
||||||
|
this.pendingEditChargeRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("accountFacilityLimits")) {
|
||||||
|
ProductPendingEditFieldMapper.writeFacilityLimitsToJson(root,
|
||||||
|
this.pendingEditFacilityLimitRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("glArrayMappings")) {
|
||||||
|
ProductPendingEditFieldMapper.writeGlArrayMappingsToJson(root,
|
||||||
|
this.pendingEditGlMappingRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
final JsonCommand replayedCommand = JsonCommand.fromJsonElement(productId, root, this.fromApiJsonHelper);
|
||||||
|
final CommandProcessingResult result = update(productId, replayedCommand);
|
||||||
|
|
||||||
|
pendingEdit.markApproved(checker);
|
||||||
|
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||||
|
|
||||||
|
final CommandSource auditEntry = findOrCreateAuditEntry(pendingEdit, productId);
|
||||||
|
auditEntry.markAsChecked(checker);
|
||||||
|
auditEntry.updateForAudit(result);
|
||||||
|
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandSource findOrCreateAuditEntry(final ProductPendingEdit pendingEdit, final Long productId) {
|
||||||
|
if (pendingEdit.getCommandSourceId() != null) {
|
||||||
|
final java.util.Optional<CommandSource> existing = this.commandSourceRepository.findById(pendingEdit.getCommandSourceId());
|
||||||
|
if (existing.isPresent()) {
|
||||||
|
return existing.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final Set<String> submittedFields = new java.util.HashSet<>(
|
||||||
|
this.pendingEditSubmittedFieldRepository.findFieldNamesByPendingEditId(pendingEdit.getId()));
|
||||||
|
final JsonObject root = new JsonObject();
|
||||||
|
root.addProperty("locale", "en");
|
||||||
|
root.addProperty("dateFormat", "yyyy-MM-dd");
|
||||||
|
ProductPendingEditFieldMapper.writeCoreFieldsToJson(root, pendingEdit.getCoreFields(), submittedFields);
|
||||||
|
ProductPendingEditFieldMapper.writeCurrentOnlyFieldsToJson(root, pendingEdit.getCurrentOnlyFields(), submittedFields);
|
||||||
|
ProductPendingEditFieldMapper.writeFacilityConfigToJson(root, pendingEdit.getFacilityConfigFields(),
|
||||||
|
submittedFields.contains("accountFacilityConfig"));
|
||||||
|
ProductPendingEditFieldMapper.writeDepositLimitKycToJson(root, pendingEdit.getDepositLimitKycFields(),
|
||||||
|
submittedFields.contains("depositLimitKyc"));
|
||||||
|
if (submittedFields.contains("glAccountMapping")) {
|
||||||
|
ProductPendingEditFieldMapper.writeGlAccountMappingToJson(root, pendingEdit.getGlAccountMappingFields());
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("charges")) {
|
||||||
|
ProductPendingEditFieldMapper.writeChargesToJson(root,
|
||||||
|
this.pendingEditChargeRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("accountFacilityLimits")) {
|
||||||
|
ProductPendingEditFieldMapper.writeFacilityLimitsToJson(root,
|
||||||
|
this.pendingEditFacilityLimitRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("glArrayMappings")) {
|
||||||
|
ProductPendingEditFieldMapper.writeGlArrayMappingsToJson(root,
|
||||||
|
this.pendingEditGlMappingRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
final String commandJson = GSON.toJson(root);
|
||||||
|
|
||||||
|
final CommandSource auditEntry = CommandSource.builder() //
|
||||||
|
.actionName("UPDATE") //
|
||||||
|
.entityName("CURRENTPRODUCT") //
|
||||||
|
.resourceGetUrl("/currentproducts/" + productId) //
|
||||||
|
.resourceId(productId) //
|
||||||
|
.commandAsJson(commandJson) //
|
||||||
|
.maker(pendingEdit.getMaker()) //
|
||||||
|
.madeOnDate(DateUtils.getAuditOffsetDateTime()) //
|
||||||
|
.status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()) //
|
||||||
|
.idempotencyKey(java.util.UUID.randomUUID().toString()) //
|
||||||
|
.sanitized(false) //
|
||||||
|
.build();
|
||||||
|
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||||
|
pendingEdit.setCommandSourceId(auditEntry.getId());
|
||||||
|
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||||
|
return auditEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult rejectEdit(final Long productId, final String rejectReason) {
|
||||||
|
|
||||||
|
final AppUser checker = this.context.authenticatedUser();
|
||||||
|
final ProductPendingEdit pendingEdit = this.productPendingEditRepository
|
||||||
|
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_CURRENT, productId, ProductPendingEdit.STATUS_PENDING)
|
||||||
|
.orElseThrow(() -> new CurrentProductNoPendingEditException(productId));
|
||||||
|
|
||||||
|
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||||
|
throw new CurrentProductSelfApprovalNotAllowedException(productId);
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingEdit.markRejected(checker, rejectReason);
|
||||||
|
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||||
|
|
||||||
|
final CommandSource auditEntry = findOrCreateAuditEntry(pendingEdit, productId);
|
||||||
|
auditEntry.markAsRejected(checker);
|
||||||
|
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder() //
|
||||||
|
.withEntityId(productId) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult delete(final Long productId) {
|
public CommandProcessingResult delete(final Long productId) {
|
||||||
|
|||||||
+28
-4
@@ -19,7 +19,14 @@
|
|||||||
package org.apache.fineract.portfolio.currentproduct.starter;
|
package org.apache.fineract.portfolio.currentproduct.starter;
|
||||||
|
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedFieldRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditChargeRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimitRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMappingRepository;
|
||||||
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductAssembler;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductAssembler;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
|
||||||
@@ -43,9 +50,17 @@ public class CurrentProductConfiguration {
|
|||||||
public CurrentProductReadPlatformService currentProductReadPlatformService(PlatformSecurityContext context,
|
public CurrentProductReadPlatformService currentProductReadPlatformService(PlatformSecurityContext context,
|
||||||
JdbcTemplate jdbcTemplate, CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
JdbcTemplate jdbcTemplate, CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
||||||
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
||||||
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
|
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository,
|
||||||
|
ProductPendingEditRepository productPendingEditRepository,
|
||||||
|
ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository,
|
||||||
|
ProductPendingEditChargeRepository pendingEditChargeRepository,
|
||||||
|
ProductPendingEditGlMappingRepository pendingEditGlMappingRepository,
|
||||||
|
ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository) {
|
||||||
return new CurrentProductReadPlatformServiceImpl(context, jdbcTemplate, currentFacilityConfigRepository,
|
return new CurrentProductReadPlatformServiceImpl(context, jdbcTemplate, currentFacilityConfigRepository,
|
||||||
currentFacilityLimitRepository, currentDepositLimitKycRepository);
|
currentFacilityLimitRepository, currentDepositLimitKycRepository,
|
||||||
|
productPendingEditRepository, pendingEditSubmittedFieldRepository,
|
||||||
|
pendingEditChargeRepository, pendingEditGlMappingRepository,
|
||||||
|
pendingEditFacilityLimitRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -56,9 +71,18 @@ public class CurrentProductConfiguration {
|
|||||||
ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
|
ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
|
||||||
CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
||||||
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
||||||
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
|
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository,
|
||||||
|
ProductPendingEditRepository productPendingEditRepository,
|
||||||
|
ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository,
|
||||||
|
ProductPendingEditChargeRepository pendingEditChargeRepository,
|
||||||
|
ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository,
|
||||||
|
ProductPendingEditGlMappingRepository pendingEditGlMappingRepository,
|
||||||
|
FromJsonHelper fromApiJsonHelper,
|
||||||
|
CommandSourceRepository commandSourceRepository) {
|
||||||
return new CurrentProductWritePlatformServiceJpaRepositoryImpl(context, currentProductRepository, currentProductDataValidator,
|
return new CurrentProductWritePlatformServiceJpaRepositoryImpl(context, currentProductRepository, currentProductDataValidator,
|
||||||
currentProductAssembler, accountMappingWritePlatformService, currentFacilityConfigRepository,
|
currentProductAssembler, accountMappingWritePlatformService, currentFacilityConfigRepository,
|
||||||
currentFacilityLimitRepository, currentDepositLimitKycRepository);
|
currentFacilityLimitRepository, currentDepositLimitKycRepository, productPendingEditRepository,
|
||||||
|
pendingEditSubmittedFieldRepository, pendingEditChargeRepository, pendingEditFacilityLimitRepository,
|
||||||
|
pendingEditGlMappingRepository, fromApiJsonHelper, commandSourceRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -95,23 +95,24 @@ public class FileContentStoreService implements ContentStoreService {
|
|||||||
|
|
||||||
final var target = Files.exists(getPath(safePath, false)) ? getPath(safePath, false) : getPath(safePath, true);
|
final var target = Files.exists(getPath(safePath, false)) ? getPath(safePath, false) : getPath(safePath, true);
|
||||||
|
|
||||||
try (var in = is) {
|
try (var in = is; var out = new FileOutputStream(target.toString())) {
|
||||||
IOUtils.copy(in, new FileOutputStream(target.toString()));
|
IOUtils.copy(in, out);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ContentStoreException(e);
|
throw new ContentStoreException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var relativePath = getRelativePath(target);
|
final var relativePath = getRelativePath(target);
|
||||||
|
|
||||||
try {
|
try (var in = getInputStream(target)) {
|
||||||
postUploadContentPolicy
|
postUploadContentPolicy.check(ContentPolicyContext.builder().path(path).inputStream(in).mimeType(mimeType).build());
|
||||||
.check(ContentPolicyContext.builder().path(path).inputStream(getInputStream(target)).mimeType(mimeType).build());
|
|
||||||
} catch (ContentPolicyException e) {
|
} catch (ContentPolicyException e) {
|
||||||
log.warn("Delete file because it didn't comply with the post-upload policy check: {} - {}", target, e.getMessage());
|
log.warn("Delete file because it didn't comply with the post-upload policy check: {} - {}", target, e.getMessage());
|
||||||
|
|
||||||
delete(relativePath.toString());
|
delete(relativePath.toString());
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ContentStoreException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return relativePath.toString();
|
return relativePath.toString();
|
||||||
|
|||||||
+24
-4
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.documentmanagement.service;
|
|||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -31,6 +32,7 @@ import org.apache.commons.lang3.Strings;
|
|||||||
import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService;
|
import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.adapter.EntitySignatureIdAdapter;
|
||||||
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateRequest;
|
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateRequest;
|
||||||
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateResponse;
|
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateResponse;
|
||||||
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentDeleteRequest;
|
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentDeleteRequest;
|
||||||
@@ -59,6 +61,7 @@ public class DocumentWritePlatformServiceImpl implements DocumentWritePlatformSe
|
|||||||
private final ContentStoreService storeService;
|
private final ContentStoreService storeService;
|
||||||
private final BusinessEventNotifierService businessEventNotifierService;
|
private final BusinessEventNotifierService businessEventNotifierService;
|
||||||
private final FineractProperties properties;
|
private final FineractProperties properties;
|
||||||
|
private final List<EntitySignatureIdAdapter> signatureIdAdapters;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -74,13 +77,30 @@ public class DocumentWritePlatformServiceImpl implements DocumentWritePlatformSe
|
|||||||
|
|
||||||
path = storeService.upload(path, request.getStream(), request.getType());
|
path = storeService.upload(path, request.getStream(), request.getType());
|
||||||
|
|
||||||
final var doc = new Document().setParentEntityType(request.getEntityType()).setParentEntityId(request.getEntityId())
|
final var docName = Optional.ofNullable(request.getName()).orElse(request.getFileName());
|
||||||
.setName(Optional.ofNullable(request.getName()).orElse(request.getFileName())).setFileName(request.getFileName())
|
|
||||||
.setSize(request.getSize()).setType(request.getType()).setDescription(request.getDescription()).setLocation(path)
|
// Some document "slots" (e.g. a client's signature) are single-slot, mirroring
|
||||||
.setStorageType(storeService.getType().getValue());
|
// m_client.image_id: re-upload should replace the existing row in place rather than
|
||||||
|
// leave the old one behind as an orphan whose ordering readers then disagree about.
|
||||||
|
// Adapters recognize their own (entityType, name) convention and own the pointer.
|
||||||
|
final var signatureAdapter = signatureIdAdapters.stream()
|
||||||
|
.filter(adapter -> adapter.accept(request.getEntityType(), docName)).findFirst();
|
||||||
|
|
||||||
|
final var doc = signatureAdapter.flatMap(adapter -> adapter.get(request.getEntityId()))
|
||||||
|
.flatMap(result -> documentRepository.findById(result.getId())).map(existing -> {
|
||||||
|
// replacing an existing single-slot document: drop the old file, reuse the row
|
||||||
|
storeService.delete(existing.getLocation());
|
||||||
|
return existing;
|
||||||
|
}).orElseGet(Document::new);
|
||||||
|
|
||||||
|
doc.setParentEntityType(request.getEntityType()).setParentEntityId(request.getEntityId()).setName(docName)
|
||||||
|
.setFileName(request.getFileName()).setSize(request.getSize()).setType(request.getType())
|
||||||
|
.setDescription(request.getDescription()).setLocation(path).setStorageType(storeService.getType().getValue());
|
||||||
|
|
||||||
documentRepository.save(doc);
|
documentRepository.save(doc);
|
||||||
|
|
||||||
|
signatureAdapter.ifPresent(adapter -> adapter.set(request.getEntityId(), doc.getId()));
|
||||||
|
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new DocumentCreatedBusinessEvent(documentMapper.map(doc)));
|
businessEventNotifierService.notifyPostBusinessEvent(new DocumentCreatedBusinessEvent(documentMapper.map(doc)));
|
||||||
|
|
||||||
return DocumentCreateResponse.builder().resourceId(doc.getId()).resourceIdentifier(request.getEntityType()).build();
|
return DocumentCreateResponse.builder().resourceId(doc.getId()).resourceIdentifier(request.getEntityType()).build();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user