Compare commits
135
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c961f713 | ||
|
|
9eac40bb5c | ||
|
|
14b2585cf5 | ||
|
|
a2fa8688ae | ||
|
|
a8ca7dd948 | ||
|
|
c8833367b9 | ||
|
|
94b0afff38 | ||
|
|
9dd4f8bedb | ||
|
|
1107610c08 | ||
|
|
2213308254 | ||
|
|
c568eb0abf | ||
|
|
1426be9531 | ||
|
|
541c6ab062 | ||
|
|
66a6a4766f | ||
|
|
b9aa29fb35 | ||
|
|
1a9adaec24 | ||
|
|
2da5a25e04 | ||
|
|
baf30cff68 | ||
|
|
f1d68d17fe | ||
|
|
84f8aa2274 | ||
|
|
7773a9e9c6 | ||
|
|
c890faee78 | ||
|
|
abc7eb22a2 | ||
|
|
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/
|
||||||
+1
-1
@@ -159,7 +159,7 @@ gitVersioning.apply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext['groovy.version'] = '4.0.17'
|
ext['groovy.version'] = '4.0.17'
|
||||||
ext['swaggerFile'] = "$rootDir/fineract-provider/build/resources/main/static/fineract.json".toString()
|
ext['swaggerFile'] = "$rootDir/fineract-provider/build/resources/main/static/fineract.json".toString().replace(" ", "%20")
|
||||||
|
|
||||||
// Apply static weaving configuration to all subprojects
|
// Apply static weaving configuration to all subprojects
|
||||||
subprojects { subproject ->
|
subprojects { subproject ->
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+1
@@ -54,6 +54,7 @@
|
|||||||
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
|
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
|
||||||
<class>org.apache.fineract.portfolio.group.domain.Group</class>
|
<class>org.apache.fineract.portfolio.group.domain.Group</class>
|
||||||
<class>org.apache.fineract.portfolio.client.domain.Client</class>
|
<class>org.apache.fineract.portfolio.client.domain.Client</class>
|
||||||
|
<class>org.apache.fineract.portfolio.client.domain.ClientKyc</class>
|
||||||
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventConfiguration</class>
|
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventConfiguration</class>
|
||||||
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
|
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
|
||||||
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
|
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
|
||||||
|
|||||||
+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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -54,6 +54,7 @@
|
|||||||
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
|
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
|
||||||
<class>org.apache.fineract.portfolio.group.domain.Group</class>
|
<class>org.apache.fineract.portfolio.group.domain.Group</class>
|
||||||
<class>org.apache.fineract.portfolio.client.domain.Client</class>
|
<class>org.apache.fineract.portfolio.client.domain.Client</class>
|
||||||
|
<class>org.apache.fineract.portfolio.client.domain.ClientKyc</class>
|
||||||
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventConfiguration</class>
|
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventConfiguration</class>
|
||||||
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
|
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
|
||||||
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
|
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
|
||||||
|
|||||||
+1
@@ -54,6 +54,7 @@
|
|||||||
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
|
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
|
||||||
<class>org.apache.fineract.portfolio.group.domain.Group</class>
|
<class>org.apache.fineract.portfolio.group.domain.Group</class>
|
||||||
<class>org.apache.fineract.portfolio.client.domain.Client</class>
|
<class>org.apache.fineract.portfolio.client.domain.Client</class>
|
||||||
|
<class>org.apache.fineract.portfolio.client.domain.ClientKyc</class>
|
||||||
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventConfiguration</class>
|
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventConfiguration</class>
|
||||||
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
|
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
|
||||||
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
|
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ task cleanupGeneratedJavaFiles() {
|
|||||||
// Configure Java compilation
|
// Configure Java compilation
|
||||||
tasks.named('compileJava') {
|
tasks.named('compileJava') {
|
||||||
outputs.cacheIf { true }
|
outputs.cacheIf { true }
|
||||||
dependsOn(buildJavaSdk, buildTypescriptAngularSdk, buildAsciidoc, cleanupGeneratedJavaFiles, licenseFormatMain, spotlessMiscApply)
|
dependsOn(buildJavaSdk, cleanupGeneratedJavaFiles, licenseFormatMain, spotlessMiscApply)
|
||||||
mustRunAfter(licenseFormatMain, cleanupGeneratedJavaFiles)
|
mustRunAfter(licenseFormatMain, cleanupGeneratedJavaFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
|
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
|
||||||
<class>org.apache.fineract.portfolio.group.domain.Group</class>
|
<class>org.apache.fineract.portfolio.group.domain.Group</class>
|
||||||
<class>org.apache.fineract.portfolio.client.domain.Client</class>
|
<class>org.apache.fineract.portfolio.client.domain.Client</class>
|
||||||
|
<class>org.apache.fineract.portfolio.client.domain.ClientKyc</class>
|
||||||
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventConfiguration</class>
|
<class>org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventConfiguration</class>
|
||||||
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
|
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
|
||||||
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
|
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
|
||||||
|
|||||||
+3
-1
@@ -436,7 +436,9 @@ public final class AccountingConstants {
|
|||||||
CASH_AT_TELLER(102, "cashAtTeller", GLAccountType.ASSET), //
|
CASH_AT_TELLER(102, "cashAtTeller", GLAccountType.ASSET), //
|
||||||
OPENING_BALANCES_TRANSFER_CONTRA(300, "openingBalancesTransferContra", GLAccountType.EQUITY), //
|
OPENING_BALANCES_TRANSFER_CONTRA(300, "openingBalancesTransferContra", GLAccountType.EQUITY), //
|
||||||
ASSET_FUND_SOURCE(103, "fundSource", GLAccountType.ASSET), //
|
ASSET_FUND_SOURCE(103, "fundSource", GLAccountType.ASSET), //
|
||||||
PAYABLE_DIVIDENDS(201, "payableDividends", GLAccountType.LIABILITY); //
|
PAYABLE_DIVIDENDS(201, "payableDividends", GLAccountType.LIABILITY), //
|
||||||
|
RESERVE_FOR_DIVIDEND(301, "reserveForDividend", GLAccountType.EQUITY), //
|
||||||
|
DIVIDEND_FORFEITED_INCOME(400, "dividendForfeitedIncome", GLAccountType.INCOME); //
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
|
|||||||
+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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+427
-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";
|
||||||
@@ -318,6 +664,78 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createAgent() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "AGENT";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/dds/agents/template";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder updateAgent(final Long agentId) {
|
||||||
|
this.actionName = "UPDATE";
|
||||||
|
this.entityName = "AGENT";
|
||||||
|
this.entityId = agentId;
|
||||||
|
this.href = "/dds/agents/" + agentId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createParty() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "PARTY";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/dds/parties/template";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder updateParty(final Long partyId) {
|
||||||
|
this.actionName = "UPDATE";
|
||||||
|
this.entityName = "PARTY";
|
||||||
|
this.entityId = partyId;
|
||||||
|
this.href = "/dds/parties/" + partyId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder closeParty(final Long partyId) {
|
||||||
|
this.actionName = "CLOSE";
|
||||||
|
this.entityName = "PARTY";
|
||||||
|
this.entityId = partyId;
|
||||||
|
this.href = "/dds/parties/" + partyId + "?command=close";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createPartyTransaction() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "PARTYTRANSACTION";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/dds/partytransactions/template";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createCommissionSlabs() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "COMMISSIONSLAB";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/dds/commissionslabs/template";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder generateDdsBatchRun() {
|
||||||
|
this.actionName = "GENERATE";
|
||||||
|
this.entityName = "DDSBATCH";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/dds/batchruns/template";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder postDdsBatchRun(final Long batchRunId) {
|
||||||
|
this.actionName = "POST";
|
||||||
|
this.entityName = "DDSBATCH";
|
||||||
|
this.entityId = batchRunId;
|
||||||
|
this.href = "/dds/batchruns/" + batchRunId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder createOfficeTransaction() {
|
public CommandWrapperBuilder createOfficeTransaction() {
|
||||||
this.actionName = "CREATE";
|
this.actionName = "CREATE";
|
||||||
this.entityName = "OFFICETRANSACTION";
|
this.entityName = "OFFICETRANSACTION";
|
||||||
@@ -1611,6 +2029,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 +3991,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
@@ -38,6 +38,7 @@ public enum JobName {
|
|||||||
GENERATE_RD_SCEHDULE("Generate Mandatory Savings Schedule"), //
|
GENERATE_RD_SCEHDULE("Generate Mandatory Savings Schedule"), //
|
||||||
GENERATE_LOANLOSS_PROVISIONING("Generate Loan Loss Provisioning"), //
|
GENERATE_LOANLOSS_PROVISIONING("Generate Loan Loss Provisioning"), //
|
||||||
POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"), //
|
POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"), //
|
||||||
|
FORFEIT_UNCLAIMED_DIVIDENDS("Forfeit Unclaimed Dividends"), //
|
||||||
UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts"), //
|
UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts"), //
|
||||||
ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS(
|
ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS(
|
||||||
"Add Accrual Transactions For Loans With Income Posted As Transactions"), //
|
"Add Accrual Transactions For Loans With Income Posted As Transactions"), //
|
||||||
@@ -61,6 +62,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;
|
||||||
|
|||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* 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.agent.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Collection;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable data object representing a DDS agent, including its collection-limit history and
|
||||||
|
* guarantors. Carries the collection/commission/security-account fields merged in from SubAgent
|
||||||
|
* (2026-08-24) - Agent now does the daily collection work directly.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class AgentData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final Long clientId;
|
||||||
|
private final String clientName;
|
||||||
|
private final String agentCode;
|
||||||
|
private final String agentNameId;
|
||||||
|
private final String agentAddress1;
|
||||||
|
private final String agentAddress2;
|
||||||
|
private final String agentCity;
|
||||||
|
private final String agentPinCode;
|
||||||
|
private final String agentTelephone;
|
||||||
|
private final String agentSavingAccountType;
|
||||||
|
private final String agentSavingAccountNo;
|
||||||
|
private final LocalDate agentOpeningDate;
|
||||||
|
private final LocalDate agentBirthDate;
|
||||||
|
private final BigDecimal agentCurrentBalance;
|
||||||
|
private final BigDecimal agentOpeningBalance;
|
||||||
|
private final String homeBranch;
|
||||||
|
private final String savingBranch;
|
||||||
|
private final Boolean tdsApplicable;
|
||||||
|
private final BigDecimal tdsPercent;
|
||||||
|
private final BigDecimal currentTds;
|
||||||
|
private final BigDecimal tdsAmount;
|
||||||
|
private final String transferBranch;
|
||||||
|
private final String transferAmountAccountType;
|
||||||
|
private final String transferAccountNo;
|
||||||
|
private final Boolean commissionCalculation;
|
||||||
|
private final BigDecimal limit;
|
||||||
|
private final BigDecimal machineRent;
|
||||||
|
private final String agentSecurityBranch;
|
||||||
|
private final String agentSecurityAccountType;
|
||||||
|
private final String agentSecurityAccountNo;
|
||||||
|
private final BigDecimal commission;
|
||||||
|
private final BigDecimal otherCommission;
|
||||||
|
private final BigDecimal agentDeposit;
|
||||||
|
private final BigDecimal festivalAdvance;
|
||||||
|
private final BigDecimal otherDeduction;
|
||||||
|
private final Collection<AgentLimitData> limits;
|
||||||
|
private final Collection<AgentGuarantorData> guarantors;
|
||||||
|
|
||||||
|
public static AgentData instance(final Long id, final Long clientId, final String clientName, final String agentCode,
|
||||||
|
final String agentNameId, final String agentAddress1, final String agentAddress2, final String agentCity,
|
||||||
|
final String agentPinCode, final String agentTelephone, final String agentSavingAccountType,
|
||||||
|
final String agentSavingAccountNo, final LocalDate agentOpeningDate, final LocalDate agentBirthDate,
|
||||||
|
final BigDecimal agentCurrentBalance, final BigDecimal agentOpeningBalance, final String homeBranch,
|
||||||
|
final String savingBranch, final Boolean tdsApplicable, final BigDecimal tdsPercent, final BigDecimal currentTds,
|
||||||
|
final BigDecimal tdsAmount, final String transferBranch, final String transferAmountAccountType,
|
||||||
|
final String transferAccountNo, final Boolean commissionCalculation, final BigDecimal limit, final BigDecimal machineRent,
|
||||||
|
final String agentSecurityBranch, final String agentSecurityAccountType, final String agentSecurityAccountNo,
|
||||||
|
final BigDecimal commission, final BigDecimal otherCommission, final BigDecimal agentDeposit,
|
||||||
|
final BigDecimal festivalAdvance, final BigDecimal otherDeduction, final Collection<AgentLimitData> limits,
|
||||||
|
final Collection<AgentGuarantorData> guarantors) {
|
||||||
|
return new AgentData(id, clientId, clientName, agentCode, agentNameId, agentAddress1, agentAddress2, agentCity, agentPinCode,
|
||||||
|
agentTelephone, agentSavingAccountType, agentSavingAccountNo, agentOpeningDate, agentBirthDate, agentCurrentBalance,
|
||||||
|
agentOpeningBalance, homeBranch, savingBranch, tdsApplicable, tdsPercent, currentTds, tdsAmount, transferBranch,
|
||||||
|
transferAmountAccountType, transferAccountNo, commissionCalculation, limit, machineRent, agentSecurityBranch,
|
||||||
|
agentSecurityAccountType, agentSecurityAccountNo, commission, otherCommission, agentDeposit, festivalAdvance,
|
||||||
|
otherDeduction, limits, guarantors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentData(final Long id, final Long clientId, final String clientName, final String agentCode, final String agentNameId,
|
||||||
|
final String agentAddress1, final String agentAddress2, final String agentCity, final String agentPinCode,
|
||||||
|
final String agentTelephone, final String agentSavingAccountType, final String agentSavingAccountNo,
|
||||||
|
final LocalDate agentOpeningDate, final LocalDate agentBirthDate, final BigDecimal agentCurrentBalance,
|
||||||
|
final BigDecimal agentOpeningBalance, final String homeBranch, final String savingBranch, final Boolean tdsApplicable,
|
||||||
|
final BigDecimal tdsPercent, final BigDecimal currentTds, final BigDecimal tdsAmount, final String transferBranch,
|
||||||
|
final String transferAmountAccountType, final String transferAccountNo, final Boolean commissionCalculation,
|
||||||
|
final BigDecimal limit, final BigDecimal machineRent, final String agentSecurityBranch,
|
||||||
|
final String agentSecurityAccountType, final String agentSecurityAccountNo, final BigDecimal commission,
|
||||||
|
final BigDecimal otherCommission, final BigDecimal agentDeposit, final BigDecimal festivalAdvance,
|
||||||
|
final BigDecimal otherDeduction, final Collection<AgentLimitData> limits, final Collection<AgentGuarantorData> guarantors) {
|
||||||
|
this.id = id;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.clientName = clientName;
|
||||||
|
this.agentCode = agentCode;
|
||||||
|
this.agentNameId = agentNameId;
|
||||||
|
this.agentAddress1 = agentAddress1;
|
||||||
|
this.agentAddress2 = agentAddress2;
|
||||||
|
this.agentCity = agentCity;
|
||||||
|
this.agentPinCode = agentPinCode;
|
||||||
|
this.agentTelephone = agentTelephone;
|
||||||
|
this.agentSavingAccountType = agentSavingAccountType;
|
||||||
|
this.agentSavingAccountNo = agentSavingAccountNo;
|
||||||
|
this.agentOpeningDate = agentOpeningDate;
|
||||||
|
this.agentBirthDate = agentBirthDate;
|
||||||
|
this.agentCurrentBalance = agentCurrentBalance;
|
||||||
|
this.agentOpeningBalance = agentOpeningBalance;
|
||||||
|
this.homeBranch = homeBranch;
|
||||||
|
this.savingBranch = savingBranch;
|
||||||
|
this.tdsApplicable = tdsApplicable;
|
||||||
|
this.tdsPercent = tdsPercent;
|
||||||
|
this.currentTds = currentTds;
|
||||||
|
this.tdsAmount = tdsAmount;
|
||||||
|
this.transferBranch = transferBranch;
|
||||||
|
this.transferAmountAccountType = transferAmountAccountType;
|
||||||
|
this.transferAccountNo = transferAccountNo;
|
||||||
|
this.commissionCalculation = commissionCalculation;
|
||||||
|
this.limit = limit;
|
||||||
|
this.machineRent = machineRent;
|
||||||
|
this.agentSecurityBranch = agentSecurityBranch;
|
||||||
|
this.agentSecurityAccountType = agentSecurityAccountType;
|
||||||
|
this.agentSecurityAccountNo = agentSecurityAccountNo;
|
||||||
|
this.commission = commission;
|
||||||
|
this.otherCommission = otherCommission;
|
||||||
|
this.agentDeposit = agentDeposit;
|
||||||
|
this.festivalAdvance = festivalAdvance;
|
||||||
|
this.otherDeduction = otherDeduction;
|
||||||
|
this.limits = limits;
|
||||||
|
this.guarantors = guarantors;
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.organisation.agent.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable data object representing a single agent guarantor.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class AgentGuarantorData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final Long clientId;
|
||||||
|
private final String guarantorName;
|
||||||
|
private final String address1;
|
||||||
|
private final String address2;
|
||||||
|
private final String city;
|
||||||
|
private final String pinCode;
|
||||||
|
private final String telephone;
|
||||||
|
|
||||||
|
public static AgentGuarantorData instance(final Long id, final Long clientId, final String guarantorName, final String address1,
|
||||||
|
final String address2, final String city, final String pinCode, final String telephone) {
|
||||||
|
return new AgentGuarantorData(id, clientId, guarantorName, address1, address2, city, pinCode, telephone);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentGuarantorData(final Long id, final Long clientId, final String guarantorName, final String address1,
|
||||||
|
final String address2, final String city, final String pinCode, final String telephone) {
|
||||||
|
this.id = id;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.guarantorName = guarantorName;
|
||||||
|
this.address1 = address1;
|
||||||
|
this.address2 = address2;
|
||||||
|
this.city = city;
|
||||||
|
this.pinCode = pinCode;
|
||||||
|
this.telephone = telephone;
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.organisation.agent.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public final class AgentLimitData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final LocalDate fromDate;
|
||||||
|
private final LocalDate toDate;
|
||||||
|
private final BigDecimal limit;
|
||||||
|
|
||||||
|
public static AgentLimitData instance(final Long id, final LocalDate fromDate, final LocalDate toDate, final BigDecimal limit) {
|
||||||
|
return new AgentLimitData(id, fromDate, toDate, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentLimitData(final Long id, final LocalDate fromDate, final LocalDate toDate, final BigDecimal limit) {
|
||||||
|
this.id = id;
|
||||||
|
this.fromDate = fromDate;
|
||||||
|
this.toDate = toDate;
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
/**
|
||||||
|
* 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.agent.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.OrderBy;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DDS (Daily Deposit Scheme) collection agent. Can represent either a person or, at the top of the
|
||||||
|
* hierarchy, the bank itself acting as its own agent. Holds its own settlement savings account and one
|
||||||
|
* or more guarantors.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_dds_agent", uniqueConstraints = { @UniqueConstraint(columnNames = { "agent_code" }, name = "agent_code_UNIQUE") })
|
||||||
|
public class Agent extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "client_id", nullable = false)
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@Column(name = "agent_code", length = 20, nullable = false)
|
||||||
|
private String agentCode;
|
||||||
|
|
||||||
|
@Column(name = "agent_name", length = 100, nullable = false)
|
||||||
|
private String agentName;
|
||||||
|
|
||||||
|
@Column(name = "agent_address1", length = 200, nullable = false)
|
||||||
|
private String agentAddress1;
|
||||||
|
|
||||||
|
@Column(name = "agent_address2", length = 200)
|
||||||
|
private String agentAddress2;
|
||||||
|
|
||||||
|
@Column(name = "agent_city", length = 100, nullable = false)
|
||||||
|
private String agentCity;
|
||||||
|
|
||||||
|
@Column(name = "agent_pincode", length = 20, nullable = false)
|
||||||
|
private String agentPinCode;
|
||||||
|
|
||||||
|
@Column(name = "agent_telephone", length = 20)
|
||||||
|
private String agentTelephone;
|
||||||
|
|
||||||
|
@Column(name = "agent_saving_account_type", length = 100, nullable = false)
|
||||||
|
private String agentSavingAccountType;
|
||||||
|
|
||||||
|
@Column(name = "agent_saving_account_no", length = 50, nullable = false)
|
||||||
|
private String agentSavingAccountNo;
|
||||||
|
|
||||||
|
@Column(name = "agent_opening_date", nullable = false)
|
||||||
|
private LocalDate agentOpeningDate;
|
||||||
|
|
||||||
|
@Column(name = "agent_birth_date", nullable = false)
|
||||||
|
private LocalDate agentBirthDate;
|
||||||
|
|
||||||
|
@Column(name = "agent_current_balance", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal agentCurrentBalance;
|
||||||
|
|
||||||
|
@Column(name = "agent_opening_balance", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal agentOpeningBalance;
|
||||||
|
|
||||||
|
// Fields below were merged in from SubAgent (2026-08-24) - the Agent itself now does the daily
|
||||||
|
// collection work directly; SubAgent no longer exists as a separate tier. Ag.Level / Upper Ag.Level
|
||||||
|
// / Collector (MLM) / CRPB Points / Level Update Date were dropped - they only ever meant anything
|
||||||
|
// for a multi-level hierarchy, which no longer exists here.
|
||||||
|
@Column(name = "home_branch", length = 100, nullable = false)
|
||||||
|
private String homeBranch;
|
||||||
|
|
||||||
|
@Column(name = "saving_branch", length = 100, nullable = false)
|
||||||
|
private String savingBranch;
|
||||||
|
|
||||||
|
@Column(name = "tds_applicable", nullable = false)
|
||||||
|
private boolean tdsApplicable;
|
||||||
|
|
||||||
|
@Column(name = "tds_percent", scale = 6, precision = 19)
|
||||||
|
private BigDecimal tdsPercent;
|
||||||
|
|
||||||
|
@Column(name = "current_tds", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal currentTds;
|
||||||
|
|
||||||
|
@Column(name = "tds_amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal tdsAmount;
|
||||||
|
|
||||||
|
@Column(name = "transfer_branch", length = 100)
|
||||||
|
private String transferBranch;
|
||||||
|
|
||||||
|
@Column(name = "transfer_amount_account_type", length = 100)
|
||||||
|
private String transferAmountAccountType;
|
||||||
|
|
||||||
|
@Column(name = "transfer_account_no", length = 50)
|
||||||
|
private String transferAccountNo;
|
||||||
|
|
||||||
|
@Column(name = "collector_commission_calculation", nullable = false)
|
||||||
|
private boolean commissionCalculation;
|
||||||
|
|
||||||
|
@Column(name = "collector_limit_amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal limitAmount;
|
||||||
|
|
||||||
|
@Column(name = "machine_rent", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal machineRent;
|
||||||
|
|
||||||
|
@Column(name = "agent_security_branch", length = 100)
|
||||||
|
private String agentSecurityBranch;
|
||||||
|
|
||||||
|
@Column(name = "agent_security_account_type", length = 100)
|
||||||
|
private String agentSecurityAccountType;
|
||||||
|
|
||||||
|
@Column(name = "agent_security_account_no", length = 50)
|
||||||
|
private String agentSecurityAccountNo;
|
||||||
|
|
||||||
|
@Column(name = "commission", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal commission;
|
||||||
|
|
||||||
|
@Column(name = "other_commission", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal otherCommission;
|
||||||
|
|
||||||
|
@Column(name = "agent_deposit", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal agentDeposit;
|
||||||
|
|
||||||
|
@Column(name = "festival_advance", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal festivalAdvance;
|
||||||
|
|
||||||
|
@Column(name = "other_deduction", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal otherDeduction;
|
||||||
|
|
||||||
|
@Column(name = "last_commission_date")
|
||||||
|
private LocalDate lastCommissionDate;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "agent", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
|
@OrderBy("id")
|
||||||
|
private List<AgentGuarantor> guarantors = new ArrayList<>();
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "agent", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
|
@OrderBy("id")
|
||||||
|
private List<AgentLimit> limits = new ArrayList<>();
|
||||||
|
|
||||||
|
protected Agent() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
private Agent(final Client client, final String agentCode, final String agentName, final String agentAddress1,
|
||||||
|
final String agentAddress2, final String agentCity, final String agentPinCode, final String agentTelephone,
|
||||||
|
final String agentSavingAccountType, final String agentSavingAccountNo, final LocalDate agentOpeningDate,
|
||||||
|
final LocalDate agentBirthDate, final BigDecimal agentOpeningBalance, final JsonCommand command) {
|
||||||
|
this.client = client;
|
||||||
|
this.agentCode = agentCode;
|
||||||
|
this.agentName = agentName;
|
||||||
|
this.agentAddress1 = agentAddress1;
|
||||||
|
this.agentAddress2 = StringUtils.defaultIfEmpty(agentAddress2, null);
|
||||||
|
this.agentCity = agentCity;
|
||||||
|
this.agentPinCode = agentPinCode;
|
||||||
|
this.agentTelephone = StringUtils.defaultIfEmpty(agentTelephone, null);
|
||||||
|
this.agentSavingAccountType = agentSavingAccountType;
|
||||||
|
this.agentSavingAccountNo = agentSavingAccountNo;
|
||||||
|
this.agentOpeningDate = agentOpeningDate;
|
||||||
|
this.agentBirthDate = agentBirthDate;
|
||||||
|
this.agentOpeningBalance = agentOpeningBalance == null ? BigDecimal.ZERO : agentOpeningBalance;
|
||||||
|
this.agentCurrentBalance = this.agentOpeningBalance;
|
||||||
|
|
||||||
|
this.homeBranch = command.stringValueOfParameterNamed("homeBranch");
|
||||||
|
this.savingBranch = command.stringValueOfParameterNamed("savingBranch");
|
||||||
|
this.tdsApplicable = command.booleanPrimitiveValueOfParameterNamed("tdsApplicable");
|
||||||
|
this.tdsPercent = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("tdsPercent");
|
||||||
|
this.currentTds = BigDecimal.ZERO;
|
||||||
|
this.tdsAmount = BigDecimal.ZERO;
|
||||||
|
this.transferBranch = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("transferBranch"), null);
|
||||||
|
this.transferAmountAccountType = StringUtils
|
||||||
|
.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("transferAmountAccountType"), null);
|
||||||
|
this.transferAccountNo = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("transferAccountNo"), null);
|
||||||
|
this.commissionCalculation = command.booleanPrimitiveValueOfParameterNamed("commissionCalculation");
|
||||||
|
this.limitAmount = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("limit"));
|
||||||
|
this.machineRent = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("machineRent"));
|
||||||
|
this.agentSecurityBranch = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("agentSecurityBranch"),
|
||||||
|
null);
|
||||||
|
this.agentSecurityAccountType = StringUtils
|
||||||
|
.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("agentSecurityAccountType"), null);
|
||||||
|
this.agentSecurityAccountNo = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("agentSecurityAccountNo"),
|
||||||
|
null);
|
||||||
|
this.commission = BigDecimal.ZERO;
|
||||||
|
this.otherCommission = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("otherCommission"));
|
||||||
|
this.agentDeposit = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("agentDeposit"));
|
||||||
|
this.festivalAdvance = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("festivalAdvance"));
|
||||||
|
this.otherDeduction = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("otherDeduction"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Agent fromJson(final Client client, final String agentCode, final JsonCommand command) {
|
||||||
|
final String agentName = command.stringValueOfParameterNamed("agentNameId");
|
||||||
|
final String agentAddress1 = command.stringValueOfParameterNamed("agentAddress1");
|
||||||
|
final String agentAddress2 = command.stringValueOfParameterNamedAllowingNull("agentAddress2");
|
||||||
|
final String agentCity = command.stringValueOfParameterNamed("agentCity");
|
||||||
|
final String agentPinCode = command.stringValueOfParameterNamed("agentPinCode");
|
||||||
|
final String agentTelephone = command.stringValueOfParameterNamedAllowingNull("agentTelephone");
|
||||||
|
final String agentSavingAccountType = command.stringValueOfParameterNamed("agentSavingAccountType");
|
||||||
|
final String agentSavingAccountNo = command.stringValueOfParameterNamed("agentSavingAccountNo");
|
||||||
|
final LocalDate agentOpeningDate = command.localDateValueOfParameterNamed("agentOpeningDate");
|
||||||
|
final LocalDate agentBirthDate = command.localDateValueOfParameterNamed("agentBirthDate");
|
||||||
|
final BigDecimal agentOpeningBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("agentOpeningBalance");
|
||||||
|
|
||||||
|
return new Agent(client, agentCode, agentName, agentAddress1, agentAddress2, agentCity, agentPinCode, agentTelephone,
|
||||||
|
agentSavingAccountType, agentSavingAccountNo, agentOpeningDate, agentBirthDate, agentOpeningBalance, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal orZero(final BigDecimal value) {
|
||||||
|
return value == null ? BigDecimal.ZERO : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addGuarantor(final AgentGuarantor guarantor) {
|
||||||
|
guarantor.setAgent(this);
|
||||||
|
this.guarantors.add(guarantor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetGuarantors() {
|
||||||
|
this.guarantors.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addLimit(final AgentLimit limit) {
|
||||||
|
limit.setAgent(this);
|
||||||
|
this.limits.add(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetLimits() {
|
||||||
|
this.limits.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a Commission Posting's amount to this agent's running commission total and stamps the date
|
||||||
|
* commission was last calculated through, so the next Commission Calculation batch only covers
|
||||||
|
* collections since then.
|
||||||
|
*/
|
||||||
|
public void postCommission(final BigDecimal amount, final LocalDate throughDate) {
|
||||||
|
this.commission = this.commission.add(amount);
|
||||||
|
this.lastCommissionDate = throughDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cash this agent has collected from parties but not yet handed over to a branch (an Agent Tran
|
||||||
|
* settlement). Raised by a Party Tran collection, lowered by an Agent Tran settlement - this is the
|
||||||
|
* figure an Agent's collection limit is enforced against.
|
||||||
|
*/
|
||||||
|
public BigDecimal credit(final BigDecimal amount) {
|
||||||
|
this.agentCurrentBalance = this.agentCurrentBalance.add(amount);
|
||||||
|
return this.agentCurrentBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal settle(final BigDecimal amount) {
|
||||||
|
this.agentCurrentBalance = this.agentCurrentBalance.subtract(amount);
|
||||||
|
return this.agentCurrentBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeClient(final Client newClient) {
|
||||||
|
this.client = newClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long clientId() {
|
||||||
|
return this.client.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> update(final JsonCommand command) {
|
||||||
|
|
||||||
|
final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
|
||||||
|
|
||||||
|
if (command.isChangeInLongParameterNamed("clientId", this.client.getId())) {
|
||||||
|
actualChanges.put("clientId", command.longValueOfParameterNamed("clientId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInStringParameterNamed("agentNameId", this.agentName)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed("agentNameId");
|
||||||
|
actualChanges.put("agentNameId", newValue);
|
||||||
|
this.agentName = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInStringParameterNamed("agentAddress1", this.agentAddress1)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed("agentAddress1");
|
||||||
|
actualChanges.put("agentAddress1", newValue);
|
||||||
|
this.agentAddress1 = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInStringParameterNamed("agentAddress2", this.agentAddress2)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed("agentAddress2");
|
||||||
|
actualChanges.put("agentAddress2", newValue);
|
||||||
|
this.agentAddress2 = StringUtils.defaultIfEmpty(newValue, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInStringParameterNamed("agentCity", this.agentCity)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed("agentCity");
|
||||||
|
actualChanges.put("agentCity", newValue);
|
||||||
|
this.agentCity = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInStringParameterNamed("agentPinCode", this.agentPinCode)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed("agentPinCode");
|
||||||
|
actualChanges.put("agentPinCode", newValue);
|
||||||
|
this.agentPinCode = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInStringParameterNamed("agentTelephone", this.agentTelephone)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed("agentTelephone");
|
||||||
|
actualChanges.put("agentTelephone", newValue);
|
||||||
|
this.agentTelephone = StringUtils.defaultIfEmpty(newValue, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInStringParameterNamed("agentSavingAccountType", this.agentSavingAccountType)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed("agentSavingAccountType");
|
||||||
|
actualChanges.put("agentSavingAccountType", newValue);
|
||||||
|
this.agentSavingAccountType = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInStringParameterNamed("agentSavingAccountNo", this.agentSavingAccountNo)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed("agentSavingAccountNo");
|
||||||
|
actualChanges.put("agentSavingAccountNo", newValue);
|
||||||
|
this.agentSavingAccountNo = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInDateParameterNamed("agentOpeningDate", this.agentOpeningDate)) {
|
||||||
|
actualChanges.put("agentOpeningDate", command.stringValueOfParameterNamed("agentOpeningDate"));
|
||||||
|
this.agentOpeningDate = command.localDateValueOfParameterNamed("agentOpeningDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInDateParameterNamed("agentBirthDate", this.agentBirthDate)) {
|
||||||
|
actualChanges.put("agentBirthDate", command.stringValueOfParameterNamed("agentBirthDate"));
|
||||||
|
this.agentBirthDate = command.localDateValueOfParameterNamed("agentBirthDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "homeBranch", this.homeBranch, v -> this.homeBranch = v);
|
||||||
|
stringChange(command, actualChanges, "savingBranch", this.savingBranch, v -> this.savingBranch = v);
|
||||||
|
|
||||||
|
if (command.isChangeInBooleanParameterNamed("tdsApplicable", this.tdsApplicable)) {
|
||||||
|
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("tdsApplicable");
|
||||||
|
actualChanges.put("tdsApplicable", newValue);
|
||||||
|
this.tdsApplicable = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("tdsPercent", this.tdsPercent)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("tdsPercent");
|
||||||
|
actualChanges.put("tdsPercent", newValue);
|
||||||
|
this.tdsPercent = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "transferBranch", this.transferBranch,
|
||||||
|
v -> this.transferBranch = StringUtils.defaultIfEmpty(v, null));
|
||||||
|
stringChange(command, actualChanges, "transferAmountAccountType", this.transferAmountAccountType,
|
||||||
|
v -> this.transferAmountAccountType = StringUtils.defaultIfEmpty(v, null));
|
||||||
|
stringChange(command, actualChanges, "transferAccountNo", this.transferAccountNo,
|
||||||
|
v -> this.transferAccountNo = StringUtils.defaultIfEmpty(v, null));
|
||||||
|
|
||||||
|
if (command.isChangeInBooleanParameterNamed("commissionCalculation", this.commissionCalculation)) {
|
||||||
|
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("commissionCalculation");
|
||||||
|
actualChanges.put("commissionCalculation", newValue);
|
||||||
|
this.commissionCalculation = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("limit", this.limitAmount)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("limit");
|
||||||
|
actualChanges.put("limit", newValue);
|
||||||
|
this.limitAmount = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("machineRent", this.machineRent)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("machineRent");
|
||||||
|
actualChanges.put("machineRent", newValue);
|
||||||
|
this.machineRent = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "agentSecurityBranch", this.agentSecurityBranch,
|
||||||
|
v -> this.agentSecurityBranch = StringUtils.defaultIfEmpty(v, null));
|
||||||
|
stringChange(command, actualChanges, "agentSecurityAccountType", this.agentSecurityAccountType,
|
||||||
|
v -> this.agentSecurityAccountType = StringUtils.defaultIfEmpty(v, null));
|
||||||
|
stringChange(command, actualChanges, "agentSecurityAccountNo", this.agentSecurityAccountNo,
|
||||||
|
v -> this.agentSecurityAccountNo = StringUtils.defaultIfEmpty(v, null));
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("otherCommission", this.otherCommission)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("otherCommission");
|
||||||
|
actualChanges.put("otherCommission", newValue);
|
||||||
|
this.otherCommission = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("agentDeposit", this.agentDeposit)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("agentDeposit");
|
||||||
|
actualChanges.put("agentDeposit", newValue);
|
||||||
|
this.agentDeposit = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("festivalAdvance", this.festivalAdvance)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("festivalAdvance");
|
||||||
|
actualChanges.put("festivalAdvance", newValue);
|
||||||
|
this.festivalAdvance = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("otherDeduction", this.otherDeduction)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("otherDeduction");
|
||||||
|
actualChanges.put("otherDeduction", newValue);
|
||||||
|
this.otherDeduction = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actualChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stringChange(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
|
||||||
|
final String existingValue, final java.util.function.Consumer<String> setter) {
|
||||||
|
if (command.isChangeInStringParameterNamed(paramName, existingValue)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed(paramName);
|
||||||
|
actualChanges.put(paramName, newValue);
|
||||||
|
setter.accept(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* 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.agent.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single guarantor recorded against an {@link Agent} (or, reused with the same shape, against a
|
||||||
|
* SubAgent). Always created/replaced as part of its parent agent's own create/update command - it has
|
||||||
|
* no independent API of its own.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_dds_agent_guarantor")
|
||||||
|
public class AgentGuarantor extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "agent_id", nullable = false)
|
||||||
|
private Agent agent;
|
||||||
|
|
||||||
|
@Column(name = "client_id", nullable = false)
|
||||||
|
private Long clientId;
|
||||||
|
|
||||||
|
@Column(name = "guarantor_name", length = 100, nullable = false)
|
||||||
|
private String guarantorName;
|
||||||
|
|
||||||
|
@Column(name = "address1", length = 200, nullable = false)
|
||||||
|
private String address1;
|
||||||
|
|
||||||
|
@Column(name = "address2", length = 200)
|
||||||
|
private String address2;
|
||||||
|
|
||||||
|
@Column(name = "city", length = 100, nullable = false)
|
||||||
|
private String city;
|
||||||
|
|
||||||
|
@Column(name = "pin_code", length = 20, nullable = false)
|
||||||
|
private String pinCode;
|
||||||
|
|
||||||
|
@Column(name = "telephone", length = 20)
|
||||||
|
private String telephone;
|
||||||
|
|
||||||
|
protected AgentGuarantor() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public AgentGuarantor(final Long clientId, final String guarantorName, final String address1, final String address2,
|
||||||
|
final String city, final String pinCode, final String telephone) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.guarantorName = guarantorName;
|
||||||
|
this.address1 = address1;
|
||||||
|
this.address2 = StringUtils.defaultIfEmpty(address2, null);
|
||||||
|
this.city = city;
|
||||||
|
this.pinCode = pinCode;
|
||||||
|
this.telephone = StringUtils.defaultIfEmpty(telephone, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAgent(final Agent agent) {
|
||||||
|
this.agent = agent;
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.organisation.agent.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 lombok.Getter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One row of an Agent's time-bound collection-limit history (From Date / To Date / Limit) - an
|
||||||
|
* Agent's authorized collection limit can change over time and every change is kept, not overwritten.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_dds_agent_limit")
|
||||||
|
public class AgentLimit extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "agent_id", nullable = false)
|
||||||
|
private Agent agent;
|
||||||
|
|
||||||
|
@Column(name = "from_date", nullable = false)
|
||||||
|
private LocalDate fromDate;
|
||||||
|
|
||||||
|
@Column(name = "to_date", nullable = false)
|
||||||
|
private LocalDate toDate;
|
||||||
|
|
||||||
|
@Column(name = "limit_amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal limitAmount;
|
||||||
|
|
||||||
|
protected AgentLimit() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public AgentLimit(final LocalDate fromDate, final LocalDate toDate, final BigDecimal limitAmount) {
|
||||||
|
this.fromDate = fromDate;
|
||||||
|
this.toDate = toDate;
|
||||||
|
this.limitAmount = limitAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAgent(final Agent agent) {
|
||||||
|
this.agent = agent;
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.organisation.agent.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||||
|
import org.springframework.dao.EmptyResultDataAccessException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link RuntimeException} thrown when a DDS agent resource is not found.
|
||||||
|
*/
|
||||||
|
public class AgentNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||||
|
|
||||||
|
public AgentNotFoundException(final Long id) {
|
||||||
|
super("error.msg.agent.id.invalid", "Agent with identifier " + id + " does not exist", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AgentNotFoundException(final Long id, final EmptyResultDataAccessException e) {
|
||||||
|
super("error.msg.agent.id.invalid", "Agent with identifier " + id + " does not exist", id, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AgentNotFoundException(final String agentCode) {
|
||||||
|
super("error.msg.agent.code.invalid", "Agent with code " + agentCode + " does not exist", agentCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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.commissionslab.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public final class CommissionSlabData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final String slabType;
|
||||||
|
private final BigDecimal slabFrom;
|
||||||
|
private final BigDecimal slabTo;
|
||||||
|
private final BigDecimal commissionPercent;
|
||||||
|
}
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* 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.commissionslab.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One row of the agent commission chart: an ACCOUNT slab (commission percentage on collection
|
||||||
|
* volume) or a COMMISSION slab (the override a senior agent earns on a sub-agent's commission).
|
||||||
|
* Append-only reference data, like the legacy screen it replaces - there is deliberately no update or
|
||||||
|
* delete API; a superseded slab range is retired by adding a new row, not editing history.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_dds_commission_slab")
|
||||||
|
public class CommissionSlab extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@Column(name = "slab_type", length = 20, nullable = false)
|
||||||
|
private String slabType;
|
||||||
|
|
||||||
|
@Column(name = "slab_from", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal slabFrom;
|
||||||
|
|
||||||
|
@Column(name = "slab_to", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal slabTo;
|
||||||
|
|
||||||
|
@Column(name = "commission_percent", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal commissionPercent;
|
||||||
|
|
||||||
|
protected CommissionSlab() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommissionSlab(final String slabType, final BigDecimal slabFrom, final BigDecimal slabTo, final BigDecimal commissionPercent) {
|
||||||
|
this.slabType = slabType;
|
||||||
|
this.slabFrom = slabFrom;
|
||||||
|
this.slabTo = slabTo;
|
||||||
|
this.commissionPercent = commissionPercent;
|
||||||
|
}
|
||||||
|
}
|
||||||
+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
|
||||||
|
}
|
||||||
+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.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);
|
||||||
|
|
||||||
|
/** Alerts for one customer inside the recurring-behaviour observation window. */
|
||||||
|
long countByClient_IdAndRaisedOnGreaterThanEqual(Long clientId, LocalDateTime since);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alerts already raised against one transaction under one rule. Used to stop the same ledger entry being marked
|
||||||
|
* twice, which would put two STR cases in front of the Principal Officer for a single transaction.
|
||||||
|
*/
|
||||||
|
List<AmlAlert> findByRule_IdAndTransactionId(Long ruleId, Long transactionId);
|
||||||
|
}
|
||||||
+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
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reviewer's judgement on a customer who repeatedly trips the AML engine.
|
||||||
|
*
|
||||||
|
* Distinct from the per-transaction disposition held on {@link AmlAlert}: that answers "was this
|
||||||
|
* transaction acceptable", this answers "is this customer's pattern of behaviour acceptable".
|
||||||
|
* {@link #alertCountAtReview} is what lets the queue clear — see the 0460 changelog.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_aml_recurring_review")
|
||||||
|
public class AmlRecurringReview {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "client_id", nullable = false)
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@Column(name = "alert_count_at_review", nullable = false)
|
||||||
|
private Integer alertCountAtReview;
|
||||||
|
|
||||||
|
@Column(name = "outcome", nullable = false, length = 30)
|
||||||
|
private String outcome;
|
||||||
|
|
||||||
|
@Column(name = "notes", nullable = false, length = 1000)
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "reviewed_by", nullable = false)
|
||||||
|
private AppUser reviewedBy;
|
||||||
|
|
||||||
|
@Column(name = "reviewed_on", nullable = false)
|
||||||
|
private LocalDateTime reviewedOn;
|
||||||
|
|
||||||
|
protected AmlRecurringReview() {}
|
||||||
|
|
||||||
|
public static AmlRecurringReview record(final Client client, final int alertCountAtReview,
|
||||||
|
final AmlRecurringReviewOutcome outcome, final String notes, final AppUser reviewedBy) {
|
||||||
|
final AmlRecurringReview review = new AmlRecurringReview();
|
||||||
|
review.client = client;
|
||||||
|
review.alertCountAtReview = alertCountAtReview;
|
||||||
|
review.outcome = outcome.name();
|
||||||
|
review.notes = notes;
|
||||||
|
review.reviewedBy = reviewedBy;
|
||||||
|
review.reviewedOn = LocalDateTime.now();
|
||||||
|
return review;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getAlertCountAtReview() {
|
||||||
|
return alertCountAtReview;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlRecurringReviewOutcome getOutcome() {
|
||||||
|
return AmlRecurringReviewOutcome.valueOf(outcome);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotes() {
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppUser getReviewedBy() {
|
||||||
|
return reviewedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getReviewedOn() {
|
||||||
|
return reviewedOn;
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.aml.domain;
|
||||||
|
|
||||||
|
/** What a reviewer concluded about a customer's repeated AML activity. */
|
||||||
|
public enum AmlRecurringReviewOutcome {
|
||||||
|
|
||||||
|
/** Behaviour explained and acceptable — nothing further, but the customer stays visible if it recurs. */
|
||||||
|
CLEARED,
|
||||||
|
/** Not conclusive; keep watching this customer more closely. */
|
||||||
|
MONITOR,
|
||||||
|
/** Warrants an STR case; the reviewer escalates the individual alerts separately. */
|
||||||
|
ESCALATED
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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 AmlRecurringReviewRepository extends JpaRepository<AmlRecurringReview, Long> {
|
||||||
|
|
||||||
|
/** Newest first, so the caller can take the head as the latest review for a customer. */
|
||||||
|
List<AmlRecurringReview> findByClient_IdOrderByReviewedOnDesc(Long clientId);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+163
@@ -0,0 +1,163 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/** Risk-category-tiered overrides of {@link #thresholdAmount}. Null on both means the rule is
|
||||||
|
* not tiered; {@link #thresholdAmount} is used for every client regardless of risk category. */
|
||||||
|
@Column(name = "threshold_amount_medium_risk", precision = 19, scale = 6)
|
||||||
|
private BigDecimal thresholdAmountMediumRisk;
|
||||||
|
|
||||||
|
@Column(name = "threshold_amount_high_risk", precision = 19, scale = 6)
|
||||||
|
private BigDecimal thresholdAmountHighRisk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declarative band the rule covers, shown on the AML Rules Master screen. Purely
|
||||||
|
* descriptive: unlike the tiered columns above these never take part in matching, so
|
||||||
|
* changing them cannot alter which transactions raise an alert. A null maximum means
|
||||||
|
* the rule is genuinely open-ended.
|
||||||
|
*/
|
||||||
|
@Column(name = "min_limit_amount", precision = 19, scale = 6)
|
||||||
|
private BigDecimal minLimitAmount;
|
||||||
|
|
||||||
|
@Column(name = "max_limit_amount", precision = 19, scale = 6)
|
||||||
|
private BigDecimal maxLimitAmount;
|
||||||
|
|
||||||
|
@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() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the live-transaction counterpart of a {@code ClientRiskRule} authored from the AML
|
||||||
|
* Rules Master screen. {@code sourceClientRiskRuleId} is the link the read side joins on, so
|
||||||
|
* the two rows are always created together and the rule code is derived from it.
|
||||||
|
*/
|
||||||
|
public static AmlTransactionRule createForRiskRule(final Long sourceClientRiskRuleId, final String description,
|
||||||
|
final AmlAppliesTo appliesTo, final AmlRuleType ruleType, final Integer windowDays,
|
||||||
|
final BigDecimal thresholdAmount, final BigDecimal minLimitAmount, final BigDecimal maxLimitAmount) {
|
||||||
|
final AmlTransactionRule rule = new AmlTransactionRule();
|
||||||
|
rule.sourceClientRiskRuleId = sourceClientRiskRuleId;
|
||||||
|
// rule_code is UNIQUE; the source rule id is unique per row, so this cannot collide.
|
||||||
|
rule.ruleCode = "CRR-" + sourceClientRiskRuleId;
|
||||||
|
rule.description = description;
|
||||||
|
rule.appliesTo = appliesTo.name();
|
||||||
|
rule.ruleType = ruleType.name();
|
||||||
|
rule.windowDays = windowDays;
|
||||||
|
rule.thresholdAmount = thresholdAmount;
|
||||||
|
rule.minLimitAmount = minLimitAmount;
|
||||||
|
rule.maxLimitAmount = maxLimitAmount;
|
||||||
|
rule.active = true;
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 BigDecimal getThresholdAmountMediumRisk() {
|
||||||
|
return thresholdAmountMediumRisk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getThresholdAmountHighRisk() {
|
||||||
|
return thresholdAmountHighRisk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMinLimitAmount() {
|
||||||
|
return minLimitAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMaxLimitAmount() {
|
||||||
|
return maxLimitAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AmlRuleType getRuleType() {
|
||||||
|
return AmlRuleType.valueOf(ruleType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getWindowDays() {
|
||||||
|
return windowDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSourceClientRiskRuleId() {
|
||||||
|
return sourceClientRiskRuleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.aml.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface AmlTransactionRuleRepository extends JpaRepository<AmlTransactionRule, Long> {
|
||||||
|
|
||||||
|
List<AmlTransactionRule> findByActiveTrueAndAppliesTo(String appliesTo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks a rule up by its code irrespective of {@code is_active}. Needed for MANUAL_REFERRAL, which is deliberately
|
||||||
|
* seeded inactive so the evaluation engine never fires it, yet must still be attachable to an alert a user raises
|
||||||
|
* by hand from the ledger.
|
||||||
|
*/
|
||||||
|
Optional<AmlTransactionRule> findByRuleCode(String ruleCode);
|
||||||
|
}
|
||||||
+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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-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";
|
||||||
@@ -175,6 +191,7 @@ public class ClientApiConstants {
|
|||||||
public static final String constitutionIdParamName = "constitutionId";
|
public static final String constitutionIdParamName = "constitutionId";
|
||||||
public static final String mainBusinessLineIdParamName = "mainBusinessLineId";
|
public static final String mainBusinessLineIdParamName = "mainBusinessLineId";
|
||||||
public static final String authorizedSignatoryParamName = "authorizedSignatory";
|
public static final String authorizedSignatoryParamName = "authorizedSignatory";
|
||||||
|
public static final String authorizedSignatoryClientIdsParamName = "authorizedSignatoryClientIds";
|
||||||
public static final String additionalInfoParamName = "additionalInfo";
|
public static final String additionalInfoParamName = "additionalInfo";
|
||||||
public static final String dateOfRegistrationParamName = "dateOfRegistration";
|
public static final String dateOfRegistrationParamName = "dateOfRegistration";
|
||||||
|
|
||||||
@@ -248,8 +265,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;
|
||||||
@@ -99,6 +105,19 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
private Boolean alternateDipVerified;
|
private Boolean alternateDipVerified;
|
||||||
private Long alternateDipLookupLogId;
|
private Long alternateDipLookupLogId;
|
||||||
private String alternateDipTimestamp;
|
private String alternateDipTimestamp;
|
||||||
|
/**
|
||||||
|
* Live DIP/MNRL status of the number, resolved from the latest lookup rather than the flag
|
||||||
|
* captured at onboarding. A customer cleared at onboarding whose number is later reported or
|
||||||
|
* reassigned would otherwise keep showing "Verified" forever.
|
||||||
|
*/
|
||||||
|
private String dipLiveStatus;
|
||||||
|
private String alternateDipLiveStatus;
|
||||||
|
/**
|
||||||
|
* Disposition of the client's most recent flagged sanctions alert, or null when they carry
|
||||||
|
* none. Like the DIP status this is resolved live: nameVerified is captured at onboarding and
|
||||||
|
* would keep reading "Clear" after the client was added to a later list snapshot.
|
||||||
|
*/
|
||||||
|
private String sanctionsAlertStatus;
|
||||||
private Boolean nameVerified;
|
private Boolean nameVerified;
|
||||||
private Long screeningLogId;
|
private Long screeningLogId;
|
||||||
private Boolean isStaff;
|
private Boolean isStaff;
|
||||||
@@ -400,13 +419,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;
|
||||||
@@ -424,6 +449,11 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
data.alternateDipVerified = clientData.alternateDipVerified;
|
data.alternateDipVerified = clientData.alternateDipVerified;
|
||||||
data.alternateDipLookupLogId = clientData.alternateDipLookupLogId;
|
data.alternateDipLookupLogId = clientData.alternateDipLookupLogId;
|
||||||
data.alternateDipTimestamp = clientData.alternateDipTimestamp;
|
data.alternateDipTimestamp = clientData.alternateDipTimestamp;
|
||||||
|
// Carried across too: these helpers rebuild ClientData field by field, so a field missing
|
||||||
|
// here is silently dropped from the payload the client view loads.
|
||||||
|
data.dipLiveStatus = clientData.dipLiveStatus;
|
||||||
|
data.alternateDipLiveStatus = clientData.alternateDipLiveStatus;
|
||||||
|
data.sanctionsAlertStatus = clientData.sanctionsAlertStatus;
|
||||||
data.nameVerified = clientData.nameVerified;
|
data.nameVerified = clientData.nameVerified;
|
||||||
data.screeningLogId = clientData.screeningLogId;
|
data.screeningLogId = clientData.screeningLogId;
|
||||||
return data;
|
return data;
|
||||||
@@ -456,13 +486,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;
|
||||||
@@ -480,6 +516,11 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
data.alternateDipVerified = clientData.alternateDipVerified;
|
data.alternateDipVerified = clientData.alternateDipVerified;
|
||||||
data.alternateDipLookupLogId = clientData.alternateDipLookupLogId;
|
data.alternateDipLookupLogId = clientData.alternateDipLookupLogId;
|
||||||
data.alternateDipTimestamp = clientData.alternateDipTimestamp;
|
data.alternateDipTimestamp = clientData.alternateDipTimestamp;
|
||||||
|
// Carried across too: these helpers rebuild ClientData field by field, so a field missing
|
||||||
|
// here is silently dropped from the payload the client view loads.
|
||||||
|
data.dipLiveStatus = clientData.dipLiveStatus;
|
||||||
|
data.alternateDipLiveStatus = clientData.alternateDipLiveStatus;
|
||||||
|
data.sanctionsAlertStatus = clientData.sanctionsAlertStatus;
|
||||||
data.nameVerified = clientData.nameVerified;
|
data.nameVerified = clientData.nameVerified;
|
||||||
data.screeningLogId = clientData.screeningLogId;
|
data.screeningLogId = clientData.screeningLogId;
|
||||||
return data;
|
return data;
|
||||||
@@ -509,7 +550,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;
|
||||||
@@ -527,6 +573,11 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
data.alternateDipVerified = clientData.alternateDipVerified;
|
data.alternateDipVerified = clientData.alternateDipVerified;
|
||||||
data.alternateDipLookupLogId = clientData.alternateDipLookupLogId;
|
data.alternateDipLookupLogId = clientData.alternateDipLookupLogId;
|
||||||
data.alternateDipTimestamp = clientData.alternateDipTimestamp;
|
data.alternateDipTimestamp = clientData.alternateDipTimestamp;
|
||||||
|
// Carried across too: these helpers rebuild ClientData field by field, so a field missing
|
||||||
|
// here is silently dropped from the payload the client view loads.
|
||||||
|
data.dipLiveStatus = clientData.dipLiveStatus;
|
||||||
|
data.alternateDipLiveStatus = clientData.alternateDipLiveStatus;
|
||||||
|
data.sanctionsAlertStatus = clientData.sanctionsAlertStatus;
|
||||||
data.nameVerified = clientData.nameVerified;
|
data.nameVerified = clientData.nameVerified;
|
||||||
data.screeningLogId = clientData.screeningLogId;
|
data.screeningLogId = clientData.screeningLogId;
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
+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.client.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public final class ClientFamilyMemberChildData implements Serializable {
|
||||||
|
private final Long id;
|
||||||
|
private final String fullName;
|
||||||
|
private final Long genderId;
|
||||||
|
private final String gender;
|
||||||
|
private final LocalDate dateOfBirth;
|
||||||
|
}
|
||||||
+3
@@ -84,6 +84,9 @@ public final class ClientFamilyMembersData implements Serializable {
|
|||||||
private final LocalDate marriageDate;
|
private final LocalDate marriageDate;
|
||||||
private final String marriageAuthority;
|
private final String marriageAuthority;
|
||||||
|
|
||||||
|
@lombok.Setter
|
||||||
|
private java.util.List<ClientFamilyMemberChildData> children;
|
||||||
|
|
||||||
// template holder
|
// template holder
|
||||||
private final Collection<CodeValueData> relationshipIdOptions;
|
private final Collection<CodeValueData> relationshipIdOptions;
|
||||||
private final Collection<CodeValueData> genderIdOptions;
|
private final Collection<CodeValueData> genderIdOptions;
|
||||||
|
|||||||
+14
@@ -34,6 +34,8 @@ public class ClientNonPersonData implements Serializable {
|
|||||||
private final CodeValueData mainBusinessLine;
|
private final CodeValueData mainBusinessLine;
|
||||||
private final String remarks;
|
private final String remarks;
|
||||||
private final String authorizedSignatory;
|
private final String authorizedSignatory;
|
||||||
|
/** Client ids behind {@link #authorizedSignatory}, comma separated and in the same order. */
|
||||||
|
private final String authorizedSignatoryClientIds;
|
||||||
private final String additionalInfo;
|
private final String additionalInfo;
|
||||||
private final LocalDate dateOfRegistration;
|
private final LocalDate dateOfRegistration;
|
||||||
|
|
||||||
@@ -62,13 +64,24 @@ public class ClientNonPersonData implements Serializable {
|
|||||||
this.constitution = null;
|
this.constitution = null;
|
||||||
this.mainBusinessLine = null;
|
this.mainBusinessLine = null;
|
||||||
this.authorizedSignatory = null;
|
this.authorizedSignatory = null;
|
||||||
|
this.authorizedSignatoryClientIds = null;
|
||||||
this.additionalInfo = null;
|
this.additionalInfo = null;
|
||||||
this.dateOfRegistration = null;
|
this.dateOfRegistration = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kept for the list/search mappers, whose SQL does not select the signatory client ids.
|
||||||
|
*/
|
||||||
public ClientNonPersonData(CodeValueData constitution, String incorpNo, LocalDate incorpValidityTillDate,
|
public ClientNonPersonData(CodeValueData constitution, String incorpNo, LocalDate incorpValidityTillDate,
|
||||||
CodeValueData mainBusinessLine, String remarks, String authorizedSignatory, String additionalInfo,
|
CodeValueData mainBusinessLine, String remarks, String authorizedSignatory, String additionalInfo,
|
||||||
LocalDate dateOfRegistration) {
|
LocalDate dateOfRegistration) {
|
||||||
|
this(constitution, incorpNo, incorpValidityTillDate, mainBusinessLine, remarks, authorizedSignatory, additionalInfo,
|
||||||
|
dateOfRegistration, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientNonPersonData(CodeValueData constitution, String incorpNo, LocalDate incorpValidityTillDate,
|
||||||
|
CodeValueData mainBusinessLine, String remarks, String authorizedSignatory, String additionalInfo,
|
||||||
|
LocalDate dateOfRegistration, String authorizedSignatoryClientIds) {
|
||||||
|
|
||||||
this.constitution = constitution;
|
this.constitution = constitution;
|
||||||
this.incorpNumber = incorpNo;
|
this.incorpNumber = incorpNo;
|
||||||
@@ -76,6 +89,7 @@ public class ClientNonPersonData implements Serializable {
|
|||||||
this.mainBusinessLine = mainBusinessLine;
|
this.mainBusinessLine = mainBusinessLine;
|
||||||
this.remarks = remarks;
|
this.remarks = remarks;
|
||||||
this.authorizedSignatory = authorizedSignatory;
|
this.authorizedSignatory = authorizedSignatory;
|
||||||
|
this.authorizedSignatoryClientIds = authorizedSignatoryClientIds;
|
||||||
this.additionalInfo = additionalInfo;
|
this.additionalInfo = additionalInfo;
|
||||||
this.dateOfRegistration = dateOfRegistration;
|
this.dateOfRegistration = dateOfRegistration;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import jakarta.persistence.JoinTable;
|
|||||||
import jakarta.persistence.ManyToMany;
|
import jakarta.persistence.ManyToMany;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.OneToMany;
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import jakarta.persistence.UniqueConstraint;
|
import jakarta.persistence.UniqueConstraint;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -77,6 +78,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 +235,28 @@ 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;
|
||||||
|
|
||||||
|
@OneToOne(mappedBy = "client", fetch = FetchType.LAZY)
|
||||||
|
private ClientKyc clientKyc;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
@@ -271,6 +294,17 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "alternate_dip_timestamp")
|
@Column(name = "alternate_dip_timestamp")
|
||||||
private String alternateDipTimestamp;
|
private String alternateDipTimestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DIP/MNRL status the number actually returned — CLEAN, SUSPECTED, DISCONNECTED or
|
||||||
|
* SERVICE_UNAVAILABLE. dipVerified alone cannot tell "checked and clean" apart from "the DoT
|
||||||
|
* service was down", which previously stored both as verified.
|
||||||
|
*/
|
||||||
|
@Column(name = "dip_status", length = 40)
|
||||||
|
private String dipStatus;
|
||||||
|
|
||||||
|
@Column(name = "alternate_dip_status", length = 40)
|
||||||
|
private String alternateDipStatus;
|
||||||
|
|
||||||
@Column(name = "name_verified")
|
@Column(name = "name_verified")
|
||||||
private Boolean nameVerified;
|
private Boolean nameVerified;
|
||||||
|
|
||||||
@@ -598,6 +632,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 +870,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+327
-10
@@ -25,13 +25,19 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.Transient;
|
||||||
import jakarta.persistence.UniqueConstraint;
|
import jakarta.persistence.UniqueConstraint;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
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
|
||||||
@@ -59,6 +65,14 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@JoinColumn(name = "client_id", nullable = false)
|
@JoinColumn(name = "client_id", nullable = false)
|
||||||
private Client client;
|
private Client client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scratch record of what the in-flight {@link #update} replaced, read back via
|
||||||
|
* {@link #getFieldsOverwrittenByLastUpdate()}. Never persisted and never read outside the
|
||||||
|
* transaction that called update(), which clears it first.
|
||||||
|
*/
|
||||||
|
@Transient
|
||||||
|
private final transient Set<String> fieldsOverwrittenByLastUpdate = new LinkedHashSet<>();
|
||||||
|
|
||||||
/** 100 = PENDING, 200 = IN_PROGRESS, 300 = VERIFIED, 400 = REJECTED, 500 = EXPIRED */
|
/** 100 = PENDING, 200 = IN_PROGRESS, 300 = VERIFIED, 400 = REJECTED, 500 = EXPIRED */
|
||||||
@Column(name = "kyc_status", nullable = false)
|
@Column(name = "kyc_status", nullable = false)
|
||||||
private Integer kycStatus;
|
private Integer kycStatus;
|
||||||
@@ -102,6 +116,36 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "pan_status", length = 20)
|
@Column(name = "pan_status", length = 20)
|
||||||
private String panStatus;
|
private String panStatus;
|
||||||
|
|
||||||
|
// ── PAN declaration ──────────────────────────────────────────────────────
|
||||||
|
// Accepted in place of a passing PAN check. The Income Tax check matches on name AND date of
|
||||||
|
// birth, so a customer whose PAN card carries a different spelling — or a different DOB — can
|
||||||
|
// never clear it however many times the officer retries. Rather than leave such a client
|
||||||
|
// permanently in KYC exception, the branch takes a signed declaration recording what differs.
|
||||||
|
// Holding that declaration is what suppresses the exception, so it is stored, not just shown.
|
||||||
|
|
||||||
|
/** True once a signed declaration has been accepted for a PAN that could not be verified. */
|
||||||
|
@Column(name = "pan_declaration_taken")
|
||||||
|
private Boolean panDeclarationTaken;
|
||||||
|
|
||||||
|
/** What the declaration covers: "NAME", "DOB", "NAME,DOB", or "NONE" when PAN simply failed. */
|
||||||
|
@Column(name = "pan_declaration_mismatch_fields", length = 100)
|
||||||
|
private String panDeclarationMismatchFields;
|
||||||
|
|
||||||
|
/** Name exactly as printed on the physical PAN card, keyed in by the officer. */
|
||||||
|
@Column(name = "pan_declaration_name_on_pan", length = 200)
|
||||||
|
private String panDeclarationNameOnPan;
|
||||||
|
|
||||||
|
/** Date of birth exactly as printed on the physical PAN card. */
|
||||||
|
@Column(name = "pan_declaration_dob_on_pan")
|
||||||
|
private LocalDate panDeclarationDobOnPan;
|
||||||
|
|
||||||
|
/** Officer's free-text note — why the mismatch exists and what proof was sighted. */
|
||||||
|
@Column(name = "pan_declaration_remarks", length = 500)
|
||||||
|
private String panDeclarationRemarks;
|
||||||
|
|
||||||
|
@Column(name = "pan_declaration_taken_on")
|
||||||
|
private LocalDate panDeclarationTakenOn;
|
||||||
|
|
||||||
// ── Aadhaar ──────────────────────────────────────────────────────────────
|
// ── Aadhaar ──────────────────────────────────────────────────────────────
|
||||||
/** Stored masked (last 4 digits visible). Full number must never be persisted as per UIDAI directive. */
|
/** Stored masked (last 4 digits visible). Full number must never be persisted as per UIDAI directive. */
|
||||||
@Column(name = "aadhaar_number_masked", length = 12)
|
@Column(name = "aadhaar_number_masked", length = 12)
|
||||||
@@ -113,6 +157,31 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "aadhaar_verified_on")
|
@Column(name = "aadhaar_verified_on")
|
||||||
private LocalDate aadhaarVerifiedOn;
|
private LocalDate aadhaarVerifiedOn;
|
||||||
|
|
||||||
|
// ── Aadhaar enrolment receipt ────────────────────────────────────────────
|
||||||
|
// The counterpart of the PAN declaration, for a customer who has applied for Aadhaar but does
|
||||||
|
// not hold one yet. UIDAI issues an enrolment slip carrying a 28-digit EID, and until Aadhaar
|
||||||
|
// itself is issued there is no number to put through the OTP flow — the check cannot pass
|
||||||
|
// however long the branch waits. Recording the receipt is what waives the exception.
|
||||||
|
|
||||||
|
/** True once the customer's Aadhaar enrolment slip has been sighted and recorded. */
|
||||||
|
@Column(name = "aadhaar_receipt_taken")
|
||||||
|
private Boolean aadhaarReceiptTaken;
|
||||||
|
|
||||||
|
/** 28-digit Enrolment ID from the slip, stored with its printed separators. */
|
||||||
|
@Column(name = "aadhaar_enrolment_id", length = 40)
|
||||||
|
private String aadhaarEnrolmentId;
|
||||||
|
|
||||||
|
/** Enrolment date printed on the slip — a receipt is temporary, so its age matters. */
|
||||||
|
@Column(name = "aadhaar_enrolment_date")
|
||||||
|
private LocalDate aadhaarEnrolmentDate;
|
||||||
|
|
||||||
|
/** Officer's note on what was sighted. */
|
||||||
|
@Column(name = "aadhaar_receipt_remarks", length = 500)
|
||||||
|
private String aadhaarReceiptRemarks;
|
||||||
|
|
||||||
|
@Column(name = "aadhaar_receipt_taken_on")
|
||||||
|
private LocalDate aadhaarReceiptTakenOn;
|
||||||
|
|
||||||
// ── GSTIN (mandatory for NON_INDIVIDUAL clients) ─────────────────────────
|
// ── GSTIN (mandatory for NON_INDIVIDUAL clients) ─────────────────────────
|
||||||
@Column(name = "gstin_number", length = 15)
|
@Column(name = "gstin_number", length = 15)
|
||||||
private String gstinNumber;
|
private String gstinNumber;
|
||||||
@@ -194,6 +263,18 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "freeze_authority", length = 100)
|
@Column(name = "freeze_authority", length = 100)
|
||||||
private String freezeAuthority;
|
private String freezeAuthority;
|
||||||
|
|
||||||
|
@Column(name = "freeze_date")
|
||||||
|
private LocalDate freezeDate;
|
||||||
|
|
||||||
|
@Column(name = "freeze_amount", precision = 19, scale = 6)
|
||||||
|
private BigDecimal freezeAmount;
|
||||||
|
|
||||||
|
@Column(name = "unfreeze_date")
|
||||||
|
private LocalDate unfreezeDate;
|
||||||
|
|
||||||
|
@Column(name = "no_debit_date")
|
||||||
|
private LocalDate noDebitDate;
|
||||||
|
|
||||||
// ── Form 60 / Form 15G/15H ───────────────────────────────────────────────
|
// ── Form 60 / Form 15G/15H ───────────────────────────────────────────────
|
||||||
@Column(name = "form60_type", length = 20)
|
@Column(name = "form60_type", length = 20)
|
||||||
private String form60Type;
|
private String form60Type;
|
||||||
@@ -240,6 +321,18 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "remarks", length = 500)
|
@Column(name = "remarks", length = 500)
|
||||||
private String remarks;
|
private String remarks;
|
||||||
|
|
||||||
|
@Column(name = "is_exception", nullable = false)
|
||||||
|
private Boolean isException;
|
||||||
|
|
||||||
|
@Column(name = "exception_reason", length = 200)
|
||||||
|
private String exceptionReason;
|
||||||
|
|
||||||
|
@Column(name = "exception_resolved", nullable = false)
|
||||||
|
private Boolean exceptionResolved;
|
||||||
|
|
||||||
|
@Column(name = "exception_resolved_on")
|
||||||
|
private LocalDate exceptionResolvedOn;
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
protected ClientKyc() {}
|
protected ClientKyc() {}
|
||||||
@@ -260,7 +353,9 @@ 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.isException = false;
|
||||||
|
this.exceptionResolved = false;
|
||||||
|
this.initiatedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClientKyc initiateKyc(final Client client, final JsonCommand command) {
|
public static ClientKyc initiateKyc(final Client client, final JsonCommand command) {
|
||||||
@@ -287,6 +382,24 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
private void applyExtendedFields(final JsonCommand command) {
|
private void applyExtendedFields(final JsonCommand command) {
|
||||||
this.panAadhaarSeedingStatus = StringUtils.defaultIfEmpty(
|
this.panAadhaarSeedingStatus = StringUtils.defaultIfEmpty(
|
||||||
command.stringValueOfParameterNamed("panAadhaarSeedingStatus"), this.panAadhaarSeedingStatus);
|
command.stringValueOfParameterNamed("panAadhaarSeedingStatus"), this.panAadhaarSeedingStatus);
|
||||||
|
final Boolean panDeclarationTakenVal = command.booleanObjectValueOfParameterNamed("panDeclarationTaken");
|
||||||
|
this.panDeclarationTaken = panDeclarationTakenVal != null ? panDeclarationTakenVal : false;
|
||||||
|
this.panDeclarationMismatchFields = StringUtils.defaultIfEmpty(
|
||||||
|
command.stringValueOfParameterNamed("panDeclarationMismatchFields"), null);
|
||||||
|
this.panDeclarationNameOnPan = StringUtils.defaultIfEmpty(
|
||||||
|
command.stringValueOfParameterNamed("panDeclarationNameOnPan"), null);
|
||||||
|
this.panDeclarationDobOnPan = command.localDateValueOfParameterNamed("panDeclarationDobOnPan");
|
||||||
|
this.panDeclarationRemarks = StringUtils.defaultIfEmpty(
|
||||||
|
command.stringValueOfParameterNamed("panDeclarationRemarks"), null);
|
||||||
|
this.panDeclarationTakenOn = command.localDateValueOfParameterNamed("panDeclarationTakenOn");
|
||||||
|
final Boolean aadhaarReceiptTakenVal = command.booleanObjectValueOfParameterNamed("aadhaarReceiptTaken");
|
||||||
|
this.aadhaarReceiptTaken = aadhaarReceiptTakenVal != null ? aadhaarReceiptTakenVal : false;
|
||||||
|
this.aadhaarEnrolmentId = StringUtils.defaultIfEmpty(
|
||||||
|
command.stringValueOfParameterNamed("aadhaarEnrolmentId"), null);
|
||||||
|
this.aadhaarEnrolmentDate = command.localDateValueOfParameterNamed("aadhaarEnrolmentDate");
|
||||||
|
this.aadhaarReceiptRemarks = StringUtils.defaultIfEmpty(
|
||||||
|
command.stringValueOfParameterNamed("aadhaarReceiptRemarks"), null);
|
||||||
|
this.aadhaarReceiptTakenOn = command.localDateValueOfParameterNamed("aadhaarReceiptTakenOn");
|
||||||
this.enterprisePan = StringUtils.defaultIfEmpty(
|
this.enterprisePan = StringUtils.defaultIfEmpty(
|
||||||
command.stringValueOfParameterNamed("enterprisePan"), null);
|
command.stringValueOfParameterNamed("enterprisePan"), null);
|
||||||
this.cinNumber = StringUtils.defaultIfEmpty(
|
this.cinNumber = StringUtils.defaultIfEmpty(
|
||||||
@@ -316,6 +429,10 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
command.stringValueOfParameterNamed("freezeReason"), null);
|
command.stringValueOfParameterNamed("freezeReason"), null);
|
||||||
this.freezeAuthority = StringUtils.defaultIfEmpty(
|
this.freezeAuthority = StringUtils.defaultIfEmpty(
|
||||||
command.stringValueOfParameterNamed("freezeAuthority"), null);
|
command.stringValueOfParameterNamed("freezeAuthority"), null);
|
||||||
|
this.freezeDate = command.localDateValueOfParameterNamed("freezeDate");
|
||||||
|
this.freezeAmount = command.bigDecimalValueOfParameterNamed("freezeAmount");
|
||||||
|
this.unfreezeDate = command.localDateValueOfParameterNamed("unfreezeDate");
|
||||||
|
this.noDebitDate = command.localDateValueOfParameterNamed("noDebitDate");
|
||||||
this.form60Type = StringUtils.defaultIfEmpty(
|
this.form60Type = StringUtils.defaultIfEmpty(
|
||||||
command.stringValueOfParameterNamed("form60Type"), null);
|
command.stringValueOfParameterNamed("form60Type"), null);
|
||||||
this.form60Date = command.localDateValueOfParameterNamed("form60Date");
|
this.form60Date = command.localDateValueOfParameterNamed("form60Date");
|
||||||
@@ -324,15 +441,24 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
command.stringValueOfParameterNamed("form15Type"), null);
|
command.stringValueOfParameterNamed("form15Type"), null);
|
||||||
this.form15Fy = StringUtils.defaultIfEmpty(
|
this.form15Fy = StringUtils.defaultIfEmpty(
|
||||||
command.stringValueOfParameterNamed("form15FY"), null);
|
command.stringValueOfParameterNamed("form15FY"), null);
|
||||||
|
Boolean isExceptionVal = command.booleanObjectValueOfParameterNamed("isException");
|
||||||
|
this.isException = isExceptionVal != null ? isExceptionVal : false;
|
||||||
|
this.exceptionReason = StringUtils.defaultIfEmpty(
|
||||||
|
command.stringValueOfParameterNamed("exceptionReason"), null);
|
||||||
|
Boolean exceptionResolvedVal = command.booleanObjectValueOfParameterNamed("exceptionResolved");
|
||||||
|
this.exceptionResolved = exceptionResolvedVal != null ? exceptionResolvedVal : false;
|
||||||
|
this.exceptionResolvedOn = command.localDateValueOfParameterNamed("exceptionResolvedOn");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> update(final JsonCommand command) {
|
public Map<String, Object> update(final JsonCommand command) {
|
||||||
final Map<String, Object> actualChanges = new LinkedHashMap<>();
|
final Map<String, Object> actualChanges = new LinkedHashMap<>();
|
||||||
|
this.fieldsOverwrittenByLastUpdate.clear();
|
||||||
|
|
||||||
final String panNumberParam = "panNumber";
|
final String panNumberParam = "panNumber";
|
||||||
if (command.isChangeInStringParameterNamed(panNumberParam, this.panNumber)) {
|
if (command.isChangeInStringParameterNamed(panNumberParam, this.panNumber)) {
|
||||||
final String newValue = command.stringValueOfParameterNamed(panNumberParam);
|
final String newValue = command.stringValueOfParameterNamed(panNumberParam);
|
||||||
actualChanges.put(panNumberParam, newValue);
|
actualChanges.put(panNumberParam, newValue);
|
||||||
|
recordOverwrite(panNumberParam, this.panNumber);
|
||||||
this.panNumber = StringUtils.defaultIfEmpty(newValue, null);
|
this.panNumber = StringUtils.defaultIfEmpty(newValue, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +466,7 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
if (command.isChangeInStringParameterNamed(aadhaarParam, this.aadhaarNumberMasked)) {
|
if (command.isChangeInStringParameterNamed(aadhaarParam, this.aadhaarNumberMasked)) {
|
||||||
final String newValue = command.stringValueOfParameterNamed(aadhaarParam);
|
final String newValue = command.stringValueOfParameterNamed(aadhaarParam);
|
||||||
actualChanges.put(aadhaarParam, newValue);
|
actualChanges.put(aadhaarParam, newValue);
|
||||||
|
recordOverwrite(aadhaarParam, this.aadhaarNumberMasked);
|
||||||
this.aadhaarNumberMasked = StringUtils.defaultIfEmpty(newValue, null);
|
this.aadhaarNumberMasked = StringUtils.defaultIfEmpty(newValue, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +474,7 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
if (command.isChangeInStringParameterNamed(gstinParam, this.gstinNumber)) {
|
if (command.isChangeInStringParameterNamed(gstinParam, this.gstinNumber)) {
|
||||||
final String newValue = command.stringValueOfParameterNamed(gstinParam);
|
final String newValue = command.stringValueOfParameterNamed(gstinParam);
|
||||||
actualChanges.put(gstinParam, newValue);
|
actualChanges.put(gstinParam, newValue);
|
||||||
|
recordOverwrite(gstinParam, this.gstinNumber);
|
||||||
this.gstinNumber = StringUtils.defaultIfEmpty(newValue, null);
|
this.gstinNumber = StringUtils.defaultIfEmpty(newValue, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,9 +482,12 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
if (command.isChangeInIntegerParameterNamed(riskParam, this.riskCategory)) {
|
if (command.isChangeInIntegerParameterNamed(riskParam, this.riskCategory)) {
|
||||||
final Integer newValue = command.integerValueOfParameterNamed(riskParam);
|
final Integer newValue = command.integerValueOfParameterNamed(riskParam);
|
||||||
actualChanges.put(riskParam, newValue);
|
actualChanges.put(riskParam, newValue);
|
||||||
|
recordOverwrite(riskParam, this.riskCategory);
|
||||||
this.riskCategory = newValue;
|
this.riskCategory = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remarks is the audit justification itself, so a change to it is deliberately not recorded
|
||||||
|
// as an overwrite — requiring remarks in order to write remarks would be circular.
|
||||||
final String remarksParam = "remarks";
|
final String remarksParam = "remarks";
|
||||||
if (command.isChangeInStringParameterNamed(remarksParam, this.remarks)) {
|
if (command.isChangeInStringParameterNamed(remarksParam, this.remarks)) {
|
||||||
final String newValue = command.stringValueOfParameterNamed(remarksParam);
|
final String newValue = command.stringValueOfParameterNamed(remarksParam);
|
||||||
@@ -366,6 +497,28 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
updateStringField(command, "panAadhaarSeedingStatus", this.panAadhaarSeedingStatus, actualChanges,
|
updateStringField(command, "panAadhaarSeedingStatus", this.panAadhaarSeedingStatus, actualChanges,
|
||||||
v -> this.panAadhaarSeedingStatus = v);
|
v -> this.panAadhaarSeedingStatus = v);
|
||||||
|
updateBooleanField(command, "panDeclarationTaken", this.panDeclarationTaken, actualChanges,
|
||||||
|
v -> this.panDeclarationTaken = v != null ? v : false);
|
||||||
|
updateStringField(command, "panDeclarationMismatchFields", this.panDeclarationMismatchFields, actualChanges,
|
||||||
|
v -> this.panDeclarationMismatchFields = v);
|
||||||
|
updateStringField(command, "panDeclarationNameOnPan", this.panDeclarationNameOnPan, actualChanges,
|
||||||
|
v -> this.panDeclarationNameOnPan = v);
|
||||||
|
updateDateField(command, "panDeclarationDobOnPan", this.panDeclarationDobOnPan, actualChanges,
|
||||||
|
v -> this.panDeclarationDobOnPan = v);
|
||||||
|
updateStringField(command, "panDeclarationRemarks", this.panDeclarationRemarks, actualChanges,
|
||||||
|
v -> this.panDeclarationRemarks = v);
|
||||||
|
updateDateField(command, "panDeclarationTakenOn", this.panDeclarationTakenOn, actualChanges,
|
||||||
|
v -> this.panDeclarationTakenOn = v);
|
||||||
|
updateBooleanField(command, "aadhaarReceiptTaken", this.aadhaarReceiptTaken, actualChanges,
|
||||||
|
v -> this.aadhaarReceiptTaken = v != null ? v : false);
|
||||||
|
updateStringField(command, "aadhaarEnrolmentId", this.aadhaarEnrolmentId, actualChanges,
|
||||||
|
v -> this.aadhaarEnrolmentId = v);
|
||||||
|
updateDateField(command, "aadhaarEnrolmentDate", this.aadhaarEnrolmentDate, actualChanges,
|
||||||
|
v -> this.aadhaarEnrolmentDate = v);
|
||||||
|
updateStringField(command, "aadhaarReceiptRemarks", this.aadhaarReceiptRemarks, actualChanges,
|
||||||
|
v -> this.aadhaarReceiptRemarks = v);
|
||||||
|
updateDateField(command, "aadhaarReceiptTakenOn", this.aadhaarReceiptTakenOn, actualChanges,
|
||||||
|
v -> this.aadhaarReceiptTakenOn = v);
|
||||||
updateStringField(command, "enterprisePan", this.enterprisePan, actualChanges, v -> this.enterprisePan = v);
|
updateStringField(command, "enterprisePan", this.enterprisePan, actualChanges, v -> this.enterprisePan = v);
|
||||||
updateStringField(command, "cinNumber", this.cinNumber, actualChanges, v -> this.cinNumber = v);
|
updateStringField(command, "cinNumber", this.cinNumber, actualChanges, v -> this.cinNumber = v);
|
||||||
updateStringField(command, "dinNumber", this.dinNumber, actualChanges, v -> this.dinNumber = v);
|
updateStringField(command, "dinNumber", this.dinNumber, actualChanges, v -> this.dinNumber = v);
|
||||||
@@ -391,12 +544,23 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
updateStringField(command, "freezeReason", this.freezeReason, actualChanges, v -> this.freezeReason = v);
|
updateStringField(command, "freezeReason", this.freezeReason, actualChanges, v -> this.freezeReason = v);
|
||||||
updateStringField(command, "freezeAuthority", this.freezeAuthority, actualChanges,
|
updateStringField(command, "freezeAuthority", this.freezeAuthority, actualChanges,
|
||||||
v -> this.freezeAuthority = v);
|
v -> this.freezeAuthority = v);
|
||||||
|
updateDateField(command, "freezeDate", this.freezeDate, actualChanges, v -> this.freezeDate = v);
|
||||||
|
updateBigDecimalField(command, "freezeAmount", this.freezeAmount, actualChanges, v -> this.freezeAmount = v);
|
||||||
|
updateDateField(command, "unfreezeDate", this.unfreezeDate, actualChanges, v -> this.unfreezeDate = v);
|
||||||
|
updateDateField(command, "noDebitDate", this.noDebitDate, actualChanges, v -> this.noDebitDate = v);
|
||||||
updateStringField(command, "form60Type", this.form60Type, actualChanges, v -> this.form60Type = v);
|
updateStringField(command, "form60Type", this.form60Type, actualChanges, v -> this.form60Type = v);
|
||||||
updateDateField(command, "form60Date", this.form60Date, actualChanges, v -> this.form60Date = v);
|
updateDateField(command, "form60Date", this.form60Date, actualChanges, v -> this.form60Date = v);
|
||||||
updateBooleanField(command, "form15Applicable", this.form15Applicable, actualChanges,
|
updateBooleanField(command, "form15Applicable", this.form15Applicable, actualChanges,
|
||||||
v -> this.form15Applicable = v);
|
v -> this.form15Applicable = v);
|
||||||
updateStringField(command, "form15Type", this.form15Type, actualChanges, v -> this.form15Type = v);
|
updateStringField(command, "form15Type", this.form15Type, actualChanges, v -> this.form15Type = v);
|
||||||
updateStringField(command, "form15FY", this.form15Fy, actualChanges, v -> this.form15Fy = v);
|
updateStringField(command, "form15FY", this.form15Fy, actualChanges, v -> this.form15Fy = v);
|
||||||
|
updateBooleanField(command, "isException", this.isException, actualChanges, v -> this.isException = v != null ? v : false);
|
||||||
|
updateStringField(command, "exceptionReason", this.exceptionReason, actualChanges, v -> this.exceptionReason = v);
|
||||||
|
updateBooleanField(command, "exceptionResolved", this.exceptionResolved, actualChanges, v -> this.exceptionResolved = v != null ? v : false);
|
||||||
|
updateDateField(command, "exceptionResolvedOn", this.exceptionResolvedOn, actualChanges, v -> this.exceptionResolvedOn = v);
|
||||||
|
updateBooleanField(command, "panVerified", this.panVerified, actualChanges, v -> this.panVerified = v != null ? v : false);
|
||||||
|
updateBooleanField(command, "aadhaarVerified", this.aadhaarVerified, actualChanges, v -> this.aadhaarVerified = v != null ? v : false);
|
||||||
|
updateBooleanField(command, "gstinVerified", this.gstinVerified, actualChanges, v -> this.gstinVerified = v != null ? v : false);
|
||||||
|
|
||||||
return actualChanges;
|
return actualChanges;
|
||||||
}
|
}
|
||||||
@@ -406,6 +570,7 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
if (command.isChangeInStringParameterNamed(param, current)) {
|
if (command.isChangeInStringParameterNamed(param, current)) {
|
||||||
final String newValue = command.stringValueOfParameterNamed(param);
|
final String newValue = command.stringValueOfParameterNamed(param);
|
||||||
changes.put(param, newValue);
|
changes.put(param, newValue);
|
||||||
|
recordOverwrite(param, current);
|
||||||
setter.accept(StringUtils.defaultIfEmpty(newValue, null));
|
setter.accept(StringUtils.defaultIfEmpty(newValue, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,6 +580,7 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
if (command.isChangeInLocalDateParameterNamed(param, current)) {
|
if (command.isChangeInLocalDateParameterNamed(param, current)) {
|
||||||
final LocalDate newValue = command.localDateValueOfParameterNamed(param);
|
final LocalDate newValue = command.localDateValueOfParameterNamed(param);
|
||||||
changes.put(param, newValue);
|
changes.put(param, newValue);
|
||||||
|
recordOverwrite(param, current);
|
||||||
setter.accept(newValue);
|
setter.accept(newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,11 +591,51 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
final Boolean newValue = command.booleanObjectValueOfParameterNamed(param);
|
final Boolean newValue = command.booleanObjectValueOfParameterNamed(param);
|
||||||
if ((newValue == null && current != null) || (newValue != null && !newValue.equals(current))) {
|
if ((newValue == null && current != null) || (newValue != null && !newValue.equals(current))) {
|
||||||
changes.put(param, newValue);
|
changes.put(param, newValue);
|
||||||
|
recordOverwrite(param, current);
|
||||||
setter.accept(newValue);
|
setter.accept(newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateBigDecimalField(final JsonCommand command, final String param, final BigDecimal current,
|
||||||
|
final Map<String, Object> changes, final java.util.function.Consumer<BigDecimal> setter) {
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed(param, current)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(param);
|
||||||
|
changes.put(param, newValue);
|
||||||
|
recordOverwrite(param, current);
|
||||||
|
setter.accept(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notes that {@code param} replaced a value the record already held. A field going from empty to
|
||||||
|
* populated is the record being completed rather than altered, so it is deliberately not
|
||||||
|
* recorded — see {@link #getFieldsOverwrittenByLastUpdate()}.
|
||||||
|
*/
|
||||||
|
private void recordOverwrite(final String param, final Object currentValue) {
|
||||||
|
final boolean wasPopulated = currentValue instanceof String text
|
||||||
|
? StringUtils.isNotBlank(text)
|
||||||
|
: currentValue != null;
|
||||||
|
if (wasPopulated) {
|
||||||
|
this.fieldsOverwrittenByLastUpdate.add(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters whose already-populated value the most recent {@link #update} replaced.
|
||||||
|
*
|
||||||
|
* <p>Distinguishes altering a record from finishing one: a non-individual KYC auto-verifies as
|
||||||
|
* soon as PAN and GSTIN pass, which happens part-way through the create wizard, and the officer
|
||||||
|
* still has to save the KYC document details afterwards. Those fields are empty at that point,
|
||||||
|
* so filling them in leaves this set empty and no audit justification is demanded — while
|
||||||
|
* changing a document number that was already on the verified record does populate it.
|
||||||
|
*
|
||||||
|
* <p>Reset at the start of every {@link #update} and never persisted: it describes one call.
|
||||||
|
*/
|
||||||
|
public Set<String> getFieldsOverwrittenByLastUpdate() {
|
||||||
|
return Collections.unmodifiableSet(this.fieldsOverwrittenByLastUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
/** Called when documents are submitted and 3rd-party verification is initiated. */
|
/** Called when documents are submitted and 3rd-party verification is initiated. */
|
||||||
public void markInProgress() {
|
public void markInProgress() {
|
||||||
this.kycStatus = ClientKycStatus.IN_PROGRESS.getValue();
|
this.kycStatus = ClientKycStatus.IN_PROGRESS.getValue();
|
||||||
@@ -456,13 +662,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 +680,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 +713,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 +791,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;
|
||||||
}
|
}
|
||||||
@@ -621,6 +836,60 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return panStatus;
|
return panStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getPanDeclarationTaken() {
|
||||||
|
return panDeclarationTaken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Null-safe form for callers deciding whether the PAN exception is waived. */
|
||||||
|
public boolean isPanDeclarationTaken() {
|
||||||
|
return Boolean.TRUE.equals(this.panDeclarationTaken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPanDeclarationMismatchFields() {
|
||||||
|
return panDeclarationMismatchFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPanDeclarationNameOnPan() {
|
||||||
|
return panDeclarationNameOnPan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getPanDeclarationDobOnPan() {
|
||||||
|
return panDeclarationDobOnPan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPanDeclarationRemarks() {
|
||||||
|
return panDeclarationRemarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getPanDeclarationTakenOn() {
|
||||||
|
return panDeclarationTakenOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getAadhaarReceiptTaken() {
|
||||||
|
return aadhaarReceiptTaken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Null-safe form for callers deciding whether the Aadhaar exception is waived. */
|
||||||
|
public boolean isAadhaarReceiptTaken() {
|
||||||
|
return Boolean.TRUE.equals(this.aadhaarReceiptTaken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAadhaarEnrolmentId() {
|
||||||
|
return aadhaarEnrolmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getAadhaarEnrolmentDate() {
|
||||||
|
return aadhaarEnrolmentDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAadhaarReceiptRemarks() {
|
||||||
|
return aadhaarReceiptRemarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getAadhaarReceiptTakenOn() {
|
||||||
|
return aadhaarReceiptTakenOn;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAadhaarNumberMasked() {
|
public String getAadhaarNumberMasked() {
|
||||||
return aadhaarNumberMasked;
|
return aadhaarNumberMasked;
|
||||||
}
|
}
|
||||||
@@ -785,6 +1054,38 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return form15Fy;
|
return form15Fy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getIsException() {
|
||||||
|
return isException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsException(Boolean isException) {
|
||||||
|
this.isException = isException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExceptionReason() {
|
||||||
|
return exceptionReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExceptionReason(String exceptionReason) {
|
||||||
|
this.exceptionReason = exceptionReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getExceptionResolved() {
|
||||||
|
return exceptionResolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExceptionResolved(Boolean exceptionResolved) {
|
||||||
|
this.exceptionResolved = exceptionResolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getExceptionResolvedOn() {
|
||||||
|
return exceptionResolvedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExceptionResolvedOn(LocalDate exceptionResolvedOn) {
|
||||||
|
this.exceptionResolvedOn = exceptionResolvedOn;
|
||||||
|
}
|
||||||
|
|
||||||
@Column(name = "is_active", nullable = false)
|
@Column(name = "is_active", nullable = false)
|
||||||
private Boolean isActive = Boolean.TRUE;
|
private Boolean isActive = Boolean.TRUE;
|
||||||
|
|
||||||
@@ -836,4 +1137,20 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public Long getVersion() {
|
public Long getVersion() {
|
||||||
return this.version;
|
return this.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalDate getFreezeDate() {
|
||||||
|
return this.freezeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getFreezeAmount() {
|
||||||
|
return this.freezeAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getUnfreezeDate() {
|
||||||
|
return this.unfreezeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getNoDebitDate() {
|
||||||
|
return this.noDebitDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+204
@@ -0,0 +1,204 @@
|
|||||||
|
/**
|
||||||
|
* 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.EnumType;
|
||||||
|
import jakarta.persistence.Enumerated;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One customer-outreach attempt against an open KYC exception.
|
||||||
|
*
|
||||||
|
* <p>Append-only by design — there is no setter-driven update path and the API exposes no PUT or
|
||||||
|
* DELETE. A contact log that can be edited after the fact is worthless as evidence, and RBI's KYC
|
||||||
|
* Master Direction expects the bank to be able to show what it told the customer and when.
|
||||||
|
*
|
||||||
|
* <p>Destinations are stored masked ({@code destinationMasked}); the full mobile number / email
|
||||||
|
* lives on {@code m_client} and is resolved server-side at send time, never accepted from the
|
||||||
|
* browser.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_client_kyc_contact_log")
|
||||||
|
public class ClientKycContactLog {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "client_id", nullable = false)
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
/** The KYC record the exception belongs to. Nullable — outreach can precede a KYC row. */
|
||||||
|
@Column(name = "kyc_id")
|
||||||
|
private Long kycId;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "channel", length = 20, nullable = false)
|
||||||
|
private KycContactChannel channel;
|
||||||
|
|
||||||
|
@Column(name = "notes", length = 1000, nullable = false)
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
@Column(name = "officer_name", length = 200)
|
||||||
|
private String officerName;
|
||||||
|
|
||||||
|
@Column(name = "logged_by_user_id")
|
||||||
|
private Long loggedByUserId;
|
||||||
|
|
||||||
|
@Column(name = "exception_reason", length = 200)
|
||||||
|
private String exceptionReason;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "delivery_status", length = 30, nullable = false)
|
||||||
|
private KycContactDeliveryStatus deliveryStatus;
|
||||||
|
|
||||||
|
@Column(name = "destination_masked", length = 120)
|
||||||
|
private String destinationMasked;
|
||||||
|
|
||||||
|
@Column(name = "provider", length = 40)
|
||||||
|
private String provider;
|
||||||
|
|
||||||
|
@Column(name = "provider_reference", length = 120)
|
||||||
|
private String providerReference;
|
||||||
|
|
||||||
|
@Column(name = "failure_reason", length = 500)
|
||||||
|
private String failureReason;
|
||||||
|
|
||||||
|
@Column(name = "message_body", length = 2000)
|
||||||
|
private String messageBody;
|
||||||
|
|
||||||
|
@Column(name = "contacted_at", nullable = false)
|
||||||
|
private LocalDateTime contactedAt;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
protected ClientKycContactLog() {
|
||||||
|
// JPA
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientKycContactLog(final Client client, final Long kycId, final KycContactChannel channel, final String notes,
|
||||||
|
final String officerName, final Long loggedByUserId, final String exceptionReason, final LocalDateTime contactedAt) {
|
||||||
|
this.client = client;
|
||||||
|
this.kycId = kycId;
|
||||||
|
this.channel = channel;
|
||||||
|
this.notes = notes;
|
||||||
|
this.officerName = officerName;
|
||||||
|
this.loggedByUserId = loggedByUserId;
|
||||||
|
this.exceptionReason = exceptionReason;
|
||||||
|
this.contactedAt = contactedAt;
|
||||||
|
this.createdAt = contactedAt;
|
||||||
|
this.deliveryStatus = KycContactDeliveryStatus.NOT_APPLICABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stamp the outcome of a platform send onto this log row. Called once, before the row is
|
||||||
|
* persisted — the entity is append-only afterwards.
|
||||||
|
*/
|
||||||
|
public void recordDeliveryOutcome(final KycContactDeliveryStatus status, final String destinationMasked, final String provider,
|
||||||
|
final String providerReference, final String failureReason, final String messageBody) {
|
||||||
|
this.deliveryStatus = status;
|
||||||
|
this.destinationMasked = destinationMasked;
|
||||||
|
this.provider = provider;
|
||||||
|
this.providerReference = providerReference;
|
||||||
|
this.failureReason = truncate(failureReason, 500);
|
||||||
|
this.messageBody = truncate(messageBody, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String truncate(final String value, final int max) {
|
||||||
|
if (value == null || value.length() <= max) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.substring(0, max - 3) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
return this.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getKycId() {
|
||||||
|
return this.kycId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KycContactChannel getChannel() {
|
||||||
|
return this.channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotes() {
|
||||||
|
return this.notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOfficerName() {
|
||||||
|
return this.officerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLoggedByUserId() {
|
||||||
|
return this.loggedByUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExceptionReason() {
|
||||||
|
return this.exceptionReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KycContactDeliveryStatus getDeliveryStatus() {
|
||||||
|
return this.deliveryStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDestinationMasked() {
|
||||||
|
return this.destinationMasked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProvider() {
|
||||||
|
return this.provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProviderReference() {
|
||||||
|
return this.providerReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFailureReason() {
|
||||||
|
return this.failureReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageBody() {
|
||||||
|
return this.messageBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getContactedAt() {
|
||||||
|
return this.contactedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return this.createdAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
public interface ClientKycContactLogRepository extends JpaRepository<ClientKycContactLog, Long> {
|
||||||
|
|
||||||
|
List<ClientKycContactLog> findByClientIdOrderByContactedAtAsc(Long clientId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every contact log for the given clients in one round trip, so the exceptions grid can render
|
||||||
|
* "last contacted" for the whole page without an N+1 query per row.
|
||||||
|
*/
|
||||||
|
@Query("SELECT l FROM ClientKycContactLog l WHERE l.client.id IN :clientIds ORDER BY l.contactedAt ASC")
|
||||||
|
List<ClientKycContactLog> findByClientIds(@Param("clientIds") List<Long> clientIds);
|
||||||
|
}
|
||||||
+12
@@ -33,6 +33,18 @@ public interface ClientKycRepository extends JpaRepository<ClientKyc, Long> {
|
|||||||
|
|
||||||
boolean existsByClientId(Long clientId);
|
boolean existsByClientId(Long clientId);
|
||||||
|
|
||||||
|
List<ClientKyc> findByIsExceptionTrueAndExceptionResolvedFalse();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolved exceptions, keyed on {@code exceptionResolved} alone.
|
||||||
|
*
|
||||||
|
* <p>Deliberately does NOT also require {@code isException = true}. Resolving an exception
|
||||||
|
* clears {@code isException} — that flag means "has an OPEN exception", so leaving it set on a
|
||||||
|
* closed one would be wrong. An earlier version of this query required both flags, which made
|
||||||
|
* every resolved record invisible in both tabs the moment it was resolved.
|
||||||
|
*/
|
||||||
|
List<ClientKyc> findByExceptionResolvedTrue();
|
||||||
|
|
||||||
@Query("SELECT k FROM ClientKyc k WHERE k.kycStatus = :status")
|
@Query("SELECT k FROM ClientKyc k WHERE k.kycStatus = :status")
|
||||||
List<ClientKyc> findAllByStatus(@Param("status") Integer status);
|
List<ClientKyc> findAllByStatus(@Param("status") Integer status);
|
||||||
|
|
||||||
|
|||||||
+7
-6
@@ -55,12 +55,13 @@ 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
|
||||||
* Count clients whose cif_number begins with the given prefix. Used by the CIF number generator
|
@Query("UPDATE Client client SET client.signatureId = :signatureId WHERE client.id = :clientId")
|
||||||
* (TTYYYYNNNNNN format) to compute the next sequence within a given (type, year) bucket.
|
void updateByIdAndSignatureId(@Param("clientId") Long clientId, @Param("signatureId") Long signatureId);
|
||||||
*/
|
|
||||||
@Query("SELECT COUNT(c) FROM Client c WHERE c.cifNumber LIKE CONCAT(:prefix, '%')")
|
@Modifying
|
||||||
long countByCifNumberPrefix(@Param("prefix") String prefix);
|
@Query("UPDATE Client client SET client.signatureId = null WHERE client.id = :clientId")
|
||||||
|
void removeSignatureId(@Param("clientId") Long clientId);
|
||||||
|
|
||||||
// ── Pre-save uniqueness probes ───────────────────────────────────────────
|
// ── Pre-save uniqueness probes ───────────────────────────────────────────
|
||||||
// Why: DB has UNIQUE on mobile_no / email_address, but we want to fail fast with a clear
|
// Why: DB has UNIQUE on mobile_no / email_address, but we want to fail fast with a clear
|
||||||
|
|||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
/**
|
||||||
|
* 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() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a rule authored from the AML Rules Master screen. Occupation and occupation-category
|
||||||
|
* are attached separately by the caller, which owns the CodeValue lookup.
|
||||||
|
*/
|
||||||
|
public static ClientRiskRule create(final String ruleName, final String reason, final Integer riskCategory,
|
||||||
|
final Integer orderPosition, final Integer legalFormId, final BigDecimal minMonthlyIncome,
|
||||||
|
final BigDecimal maxMonthlyIncome, final BigDecimal minAnnualIncome, final BigDecimal maxAnnualIncome,
|
||||||
|
final BigDecimal minAnnualTurnover, final BigDecimal maxAnnualTurnover, final String amlCheckStatus,
|
||||||
|
final String bannedEntityMatch, final String willfulDefaulter, final String ecgcSalFlag,
|
||||||
|
final String dormantFlag) {
|
||||||
|
final ClientRiskRule rule = new ClientRiskRule();
|
||||||
|
rule.ruleName = ruleName;
|
||||||
|
rule.reason = reason;
|
||||||
|
rule.riskCategory = riskCategory;
|
||||||
|
rule.orderPosition = orderPosition;
|
||||||
|
rule.legalFormId = legalFormId;
|
||||||
|
rule.minMonthlyIncome = minMonthlyIncome;
|
||||||
|
rule.maxMonthlyIncome = maxMonthlyIncome;
|
||||||
|
rule.minAnnualIncome = minAnnualIncome;
|
||||||
|
rule.maxAnnualIncome = maxAnnualIncome;
|
||||||
|
rule.minAnnualTurnover = minAnnualTurnover;
|
||||||
|
rule.maxAnnualTurnover = maxAnnualTurnover;
|
||||||
|
rule.amlCheckStatus = amlCheckStatus;
|
||||||
|
rule.bannedEntityMatch = bannedEntityMatch;
|
||||||
|
rule.willfulDefaulter = willfulDefaulter;
|
||||||
|
rule.ecgcSalFlag = ecgcSalFlag;
|
||||||
|
rule.dormantFlag = dormantFlag;
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOccupationCategory(final CodeValue occupationCategory) {
|
||||||
|
this.occupationCategory = occupationCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOccupation(final CodeValue occupation) {
|
||||||
|
this.occupation = occupation;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.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();
|
||||||
|
|
||||||
|
/** Highest position in use, so a new rule can be appended to the end of the catalog. */
|
||||||
|
@Query("SELECT MAX(r.orderPosition) FROM ClientRiskRule r")
|
||||||
|
Integer findMaxOrderPosition();
|
||||||
|
|
||||||
|
boolean existsByRuleNameIgnoreCase(String ruleName);
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.client.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The channel a KYC officer used (or asked the platform to use) to reach a customer about an open
|
||||||
|
* KYC exception.
|
||||||
|
*
|
||||||
|
* <p>The critical distinction is {@link #isPlatformSent()}:
|
||||||
|
* <ul>
|
||||||
|
* <li><b>Offline channels</b> ({@code PHONE_CALL}, {@code BRANCH_VISIT}, {@code FIELD_VISIT}) are
|
||||||
|
* recorded after the fact — the officer already did the thing, the platform only writes it
|
||||||
|
* down.</li>
|
||||||
|
* <li><b>Sending channels</b> ({@code SMS}, {@code EMAIL}, {@code WHATSAPP}) cause the platform to
|
||||||
|
* actually transmit a notice to the customer at save time.</li>
|
||||||
|
* </ul>
|
||||||
|
* Mixing these up would let a "logged" row imply the customer was told something they never
|
||||||
|
* received, so every call site branches on this flag rather than on the channel name.
|
||||||
|
*/
|
||||||
|
public enum KycContactChannel {
|
||||||
|
|
||||||
|
PHONE_CALL("Phone Call", false),
|
||||||
|
SMS("SMS", true),
|
||||||
|
EMAIL("Email", true),
|
||||||
|
WHATSAPP("WhatsApp Message", true),
|
||||||
|
BRANCH_VISIT("In-Branch Visit", false),
|
||||||
|
FIELD_VISIT("Agent Field Visit", false);
|
||||||
|
|
||||||
|
private final String displayLabel;
|
||||||
|
private final boolean platformSent;
|
||||||
|
|
||||||
|
KycContactChannel(final String displayLabel, final boolean platformSent) {
|
||||||
|
this.displayLabel = displayLabel;
|
||||||
|
this.platformSent = platformSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayLabel() {
|
||||||
|
return this.displayLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** True when saving a log of this channel must actually transmit a message to the customer. */
|
||||||
|
public boolean isPlatformSent() {
|
||||||
|
return this.platformSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lenient parse of whatever the UI sent. Accepts the enum name ({@code SMS}) as well as the
|
||||||
|
* display label, and tolerates the descriptive suffixes the dialog uses — e.g.
|
||||||
|
* {@code "SMS (Aadhaar OTP request)"} and {@code "Email (PAN Link request)"} — because those
|
||||||
|
* labels describe the *purpose* of the message, not a different channel.
|
||||||
|
*
|
||||||
|
* @return the matching channel, or {@code null} when nothing matches.
|
||||||
|
*/
|
||||||
|
public static KycContactChannel fromString(final String raw) {
|
||||||
|
if (raw == null || raw.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String normalised = raw.trim().toUpperCase();
|
||||||
|
for (final KycContactChannel channel : values()) {
|
||||||
|
if (normalised.equals(channel.name()) || normalised.equals(channel.displayLabel.toUpperCase())) {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Prefix match handles the "(…request)" suffixes. Longest label first so WHATSAPP's
|
||||||
|
// "WHATSAPP MESSAGE" is preferred over a shorter accidental prefix.
|
||||||
|
KycContactChannel best = null;
|
||||||
|
for (final KycContactChannel channel : values()) {
|
||||||
|
final String label = channel.displayLabel.toUpperCase();
|
||||||
|
if (normalised.startsWith(label) && (best == null || label.length() > best.displayLabel.length())) {
|
||||||
|
best = channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.client.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outcome of a platform-sent KYC contact notice.
|
||||||
|
*
|
||||||
|
* <p>Every state other than {@link #SENT} means the customer was <em>not</em> reached. The officer
|
||||||
|
* is shown the distinction explicitly, because a contact log that silently swallows a gateway
|
||||||
|
* failure would understate the bank's outreach obligation during a KYC audit.
|
||||||
|
*/
|
||||||
|
public enum KycContactDeliveryStatus {
|
||||||
|
|
||||||
|
/** Offline channel — nothing was transmitted and nothing was supposed to be. */
|
||||||
|
NOT_APPLICABLE,
|
||||||
|
|
||||||
|
/** The gateway accepted the message for delivery. */
|
||||||
|
SENT,
|
||||||
|
|
||||||
|
/** The gateway was reached but rejected the message, or the call errored. */
|
||||||
|
FAILED,
|
||||||
|
|
||||||
|
/** The customer has no mobile number / email address on file for this channel. */
|
||||||
|
SKIPPED_NO_DESTINATION,
|
||||||
|
|
||||||
|
/** No gateway is configured for this channel in this deployment. */
|
||||||
|
PROVIDER_NOT_CONFIGURED;
|
||||||
|
|
||||||
|
public boolean isDelivered() {
|
||||||
|
return this == SENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
+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() {}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.dds.batch.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Collection;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
public final class BatchRunData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final String jobType;
|
||||||
|
private final String branchScope;
|
||||||
|
private final LocalDate jobDate;
|
||||||
|
private final Long agentFrom;
|
||||||
|
private final Long agentTo;
|
||||||
|
private final Long partyFrom;
|
||||||
|
private final Long partyTo;
|
||||||
|
private final String status;
|
||||||
|
private final Integer totalRows;
|
||||||
|
private final BigDecimal totalAmount;
|
||||||
|
private final LocalDate generatedOnDate;
|
||||||
|
private final LocalDate postedOnDate;
|
||||||
|
private final Collection<BatchRunRowData> rows;
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.dds.batch.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public final class BatchRunRowData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final Long partyId;
|
||||||
|
private final String partyCode;
|
||||||
|
private final String partyName;
|
||||||
|
private final Long agentId;
|
||||||
|
private final String agentCode;
|
||||||
|
private final BigDecimal amount;
|
||||||
|
}
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* 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.dds.batch.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.OrderBy;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One Calculate-then-Post batch run - the deliberate two-step control from the legacy Interest Part
|
||||||
|
* menu (Interest Calculation -> Interest Posting, OD Interest Calculation -> OD Interest Posting,
|
||||||
|
* Commission Calculation -> Commission Posting): a run is generated (rows computed and saved as a
|
||||||
|
* preview, nothing else changes), then posted (a separate, explicit step that applies exactly the
|
||||||
|
* rows that were previewed - never a fresh recalculation, so what gets applied is always what was
|
||||||
|
* seen).
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_dds_batch_run")
|
||||||
|
public class BatchRun extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@Column(name = "job_type", length = 40, nullable = false)
|
||||||
|
private String jobType;
|
||||||
|
|
||||||
|
@Column(name = "branch_scope", length = 20, nullable = false)
|
||||||
|
private String branchScope;
|
||||||
|
|
||||||
|
@Column(name = "job_date", nullable = false)
|
||||||
|
private LocalDate jobDate;
|
||||||
|
|
||||||
|
@Column(name = "agent_from", nullable = false)
|
||||||
|
private Long agentFrom;
|
||||||
|
|
||||||
|
@Column(name = "agent_to", nullable = false)
|
||||||
|
private Long agentTo;
|
||||||
|
|
||||||
|
@Column(name = "party_from")
|
||||||
|
private Long partyFrom;
|
||||||
|
|
||||||
|
@Column(name = "party_to")
|
||||||
|
private Long partyTo;
|
||||||
|
|
||||||
|
@Column(name = "status", length = 20, nullable = false)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "total_rows", nullable = false)
|
||||||
|
private Integer totalRows;
|
||||||
|
|
||||||
|
@Column(name = "total_amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal totalAmount;
|
||||||
|
|
||||||
|
@Column(name = "generated_on_date", nullable = false)
|
||||||
|
private LocalDate generatedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "posted_on_date")
|
||||||
|
private LocalDate postedOnDate;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "batchRun", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
|
@OrderBy("id")
|
||||||
|
private List<BatchRunRow> rows = new ArrayList<>();
|
||||||
|
|
||||||
|
protected BatchRun() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchRun(final String jobType, final String branchScope, final LocalDate jobDate, final Long agentFrom,
|
||||||
|
final Long agentTo, final Long partyFrom, final Long partyTo, final LocalDate generatedOnDate) {
|
||||||
|
this.jobType = jobType;
|
||||||
|
this.branchScope = branchScope;
|
||||||
|
this.jobDate = jobDate;
|
||||||
|
this.agentFrom = agentFrom;
|
||||||
|
this.agentTo = agentTo;
|
||||||
|
this.partyFrom = partyFrom;
|
||||||
|
this.partyTo = partyTo;
|
||||||
|
this.status = "CALCULATED";
|
||||||
|
this.totalRows = 0;
|
||||||
|
this.totalAmount = BigDecimal.ZERO;
|
||||||
|
this.generatedOnDate = generatedOnDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addRow(final BatchRunRow row) {
|
||||||
|
row.setBatchRun(this);
|
||||||
|
this.rows.add(row);
|
||||||
|
this.totalRows = this.rows.size();
|
||||||
|
this.totalAmount = this.totalAmount.add(row.getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPosted() {
|
||||||
|
return "POSTED".equals(this.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markPosted(final LocalDate postedOnDate) {
|
||||||
|
this.status = "POSTED";
|
||||||
|
this.postedOnDate = postedOnDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 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.dds.batch.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 lombok.Getter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One previewed/posted line of a {@link BatchRun}: for an interest job, one Party's accrued interest;
|
||||||
|
* for a commission job, one Agent's commission on their collected volume (partyId is null there).
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_dds_batch_run_row")
|
||||||
|
public class BatchRunRow extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "batch_run_id", nullable = false)
|
||||||
|
private BatchRun batchRun;
|
||||||
|
|
||||||
|
@Column(name = "party_id")
|
||||||
|
private Long partyId;
|
||||||
|
|
||||||
|
@Column(name = "agent_id", nullable = false)
|
||||||
|
private Long agentId;
|
||||||
|
|
||||||
|
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
protected BatchRunRow() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchRunRow(final Long partyId, final Long agentId, final BigDecimal amount) {
|
||||||
|
this.partyId = partyId;
|
||||||
|
this.agentId = agentId;
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBatchRun(final BatchRun batchRun) {
|
||||||
|
this.batchRun = batchRun;
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.dds.batch.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when Post is attempted on a batch run that has already been posted - a run applies once,
|
||||||
|
* matching the calculate-then-post safety control (never re-post the same preview).
|
||||||
|
*/
|
||||||
|
public class BatchRunAlreadyPostedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public BatchRunAlreadyPostedException(final Long batchRunId) {
|
||||||
|
super("error.msg.ddsbatchrun.already.posted", "DDS batch run " + batchRunId + " has already been posted", batchRunId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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.dds.batch.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||||
|
|
||||||
|
public class BatchRunNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||||
|
|
||||||
|
public BatchRunNotFoundException(final Long id) {
|
||||||
|
super("error.msg.ddsbatchrun.id.invalid", "DDS batch run with identifier " + id + " does not exist", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* 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.dds.party.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Collection;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable data object representing a DDS Party (customer scheme account), including its nominees.
|
||||||
|
* Built via the Lombok builder rather than a long positional constructor - the field count here makes
|
||||||
|
* positional args unreadable and error-prone to keep in sync.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
public final class PartyData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final Long clientId;
|
||||||
|
private final String clientName;
|
||||||
|
private final Long agentId;
|
||||||
|
private final String agentCode;
|
||||||
|
private final String agentName;
|
||||||
|
private final Long savingsAccountId;
|
||||||
|
private final String subType;
|
||||||
|
private final String partyCode;
|
||||||
|
private final String lastAccount;
|
||||||
|
private final Boolean nominal;
|
||||||
|
private final String aadharNo;
|
||||||
|
private final String partyName;
|
||||||
|
private final String partyName1;
|
||||||
|
private final String address1;
|
||||||
|
private final String address2;
|
||||||
|
private final String address3;
|
||||||
|
private final String city;
|
||||||
|
private final String pincode;
|
||||||
|
private final String gender;
|
||||||
|
private final String panCardNo;
|
||||||
|
private final String telephone;
|
||||||
|
private final String mobileNoSms;
|
||||||
|
private final String operation;
|
||||||
|
private final LocalDate birthDate;
|
||||||
|
private final String occupation;
|
||||||
|
private final String risk;
|
||||||
|
private final String ckycId;
|
||||||
|
private final String adultMinor;
|
||||||
|
private final LocalDate minorDate;
|
||||||
|
private final Long guardianClientId;
|
||||||
|
private final String minAccountOpt;
|
||||||
|
private final String relation;
|
||||||
|
private final LocalDate openingDate;
|
||||||
|
private final Integer month;
|
||||||
|
private final LocalDate maturityDate;
|
||||||
|
private final LocalDate reopenDate;
|
||||||
|
private final BigDecimal depositAmount;
|
||||||
|
private final BigDecimal dailyDepositAmount;
|
||||||
|
private final Integer days;
|
||||||
|
private final String ledgerNo;
|
||||||
|
private final BigDecimal maturityAmount;
|
||||||
|
private final BigDecimal interestRate;
|
||||||
|
private final String withdrawType;
|
||||||
|
private final Boolean od1Flag;
|
||||||
|
private final Boolean od2Flag;
|
||||||
|
private final Boolean lien;
|
||||||
|
private final LocalDate payDate;
|
||||||
|
private final BigDecimal paymentAmount;
|
||||||
|
private final BigDecimal currentBalance;
|
||||||
|
private final BigDecimal openingBalance;
|
||||||
|
private final Boolean isSmsActive;
|
||||||
|
private final Boolean isEmailActive;
|
||||||
|
private final BigDecimal interestCalc;
|
||||||
|
private final LocalDate interestDate;
|
||||||
|
private final String spDescription;
|
||||||
|
private final String freezeCode;
|
||||||
|
private final String advCode;
|
||||||
|
private final String creditToBranch;
|
||||||
|
private final String creditToAccountType;
|
||||||
|
private final String creditToAccount;
|
||||||
|
private final Boolean nomineeApplicable;
|
||||||
|
private final String nomineeSplitMode;
|
||||||
|
private final String status;
|
||||||
|
private final LocalDate closeDate;
|
||||||
|
private final Collection<PartyNomineeData> nominees;
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.dds.party.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public final class PartyNomineeData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final LocalDate nominationDate;
|
||||||
|
private final String name;
|
||||||
|
private final String mobileNo;
|
||||||
|
private final LocalDate birthDate;
|
||||||
|
private final Boolean minor;
|
||||||
|
private final String relation;
|
||||||
|
private final String gender;
|
||||||
|
private final String guardianName;
|
||||||
|
|
||||||
|
public static PartyNomineeData instance(final Long id, final LocalDate nominationDate, final String name, final String mobileNo,
|
||||||
|
final LocalDate birthDate, final Boolean minor, final String relation, final String gender, final String guardianName) {
|
||||||
|
return new PartyNomineeData(id, nominationDate, name, mobileNo, birthDate, minor, relation, gender, guardianName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PartyNomineeData(final Long id, final LocalDate nominationDate, final String name, final String mobileNo,
|
||||||
|
final LocalDate birthDate, final Boolean minor, final String relation, final String gender, final String guardianName) {
|
||||||
|
this.id = id;
|
||||||
|
this.nominationDate = nominationDate;
|
||||||
|
this.name = name;
|
||||||
|
this.mobileNo = mobileNo;
|
||||||
|
this.birthDate = birthDate;
|
||||||
|
this.minor = minor;
|
||||||
|
this.relation = relation;
|
||||||
|
this.gender = gender;
|
||||||
|
this.guardianName = guardianName;
|
||||||
|
}
|
||||||
|
}
|
||||||
+569
@@ -0,0 +1,569 @@
|
|||||||
|
/**
|
||||||
|
* 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.dds.party.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.OrderBy;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.organisation.agent.domain.Agent;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DDS Party - the customer's daily-deposit scheme account, linked to the Agent who collects for it.
|
||||||
|
* Carries the scheme's KYC, terms and two-stage overdue flags, plus a nominee list.
|
||||||
|
*
|
||||||
|
* The underlying RD (recurring deposit) account this scheme conceptually reuses is not wired up yet -
|
||||||
|
* {@code savingsAccountId} is a nullable placeholder for that future linkage (see the DDS architecture
|
||||||
|
* decision to reuse the RD engine for the deposit half). Until then the scheme/balance fields below
|
||||||
|
* are this record's own, not mirrored from any m_savings_account row.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_dds_party", uniqueConstraints = { @UniqueConstraint(columnNames = { "party_code" }, name = "party_code_UNIQUE") })
|
||||||
|
public class Party extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
/** DDS accounts carry a flat interest rate unless the officer explicitly overrides it at creation. */
|
||||||
|
private static final BigDecimal DEFAULT_INTEREST_RATE = new BigDecimal("2");
|
||||||
|
|
||||||
|
public static final String STATUS_ACTIVE = "ACTIVE";
|
||||||
|
public static final String STATUS_CLOSED = "CLOSED";
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "client_id", nullable = false)
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "agent_id", nullable = false)
|
||||||
|
private Agent agent;
|
||||||
|
|
||||||
|
@Column(name = "savings_account_id")
|
||||||
|
private Long savingsAccountId;
|
||||||
|
|
||||||
|
@Column(name = "sub_type", length = 60, nullable = false)
|
||||||
|
private String subType;
|
||||||
|
|
||||||
|
@Column(name = "party_code", length = 20, nullable = false)
|
||||||
|
private String partyCode;
|
||||||
|
|
||||||
|
@Column(name = "last_account", length = 50)
|
||||||
|
private String lastAccount;
|
||||||
|
|
||||||
|
@Column(name = "nominal", nullable = false)
|
||||||
|
private boolean nominal;
|
||||||
|
|
||||||
|
@Column(name = "aadhar_no", length = 20)
|
||||||
|
private String aadharNo;
|
||||||
|
|
||||||
|
@Column(name = "party_name", length = 100, nullable = false)
|
||||||
|
private String partyName;
|
||||||
|
|
||||||
|
@Column(name = "party_name1", length = 100)
|
||||||
|
private String partyName1;
|
||||||
|
|
||||||
|
@Column(name = "address1", length = 200, nullable = false)
|
||||||
|
private String address1;
|
||||||
|
|
||||||
|
@Column(name = "address2", length = 200)
|
||||||
|
private String address2;
|
||||||
|
|
||||||
|
@Column(name = "address3", length = 200)
|
||||||
|
private String address3;
|
||||||
|
|
||||||
|
@Column(name = "city", length = 100, nullable = false)
|
||||||
|
private String city;
|
||||||
|
|
||||||
|
@Column(name = "pincode", length = 20, nullable = false)
|
||||||
|
private String pincode;
|
||||||
|
|
||||||
|
@Column(name = "gender", length = 20)
|
||||||
|
private String gender;
|
||||||
|
|
||||||
|
@Column(name = "pan_card_no", length = 20)
|
||||||
|
private String panCardNo;
|
||||||
|
|
||||||
|
@Column(name = "telephone", length = 20)
|
||||||
|
private String telephone;
|
||||||
|
|
||||||
|
@Column(name = "mobile_no_sms", length = 20, nullable = false)
|
||||||
|
private String mobileNoSms;
|
||||||
|
|
||||||
|
@Column(name = "operation", length = 20, nullable = false)
|
||||||
|
private String operation;
|
||||||
|
|
||||||
|
@Column(name = "birth_date")
|
||||||
|
private LocalDate birthDate;
|
||||||
|
|
||||||
|
@Column(name = "occupation", length = 40)
|
||||||
|
private String occupation;
|
||||||
|
|
||||||
|
@Column(name = "risk", length = 20)
|
||||||
|
private String risk;
|
||||||
|
|
||||||
|
@Column(name = "ckyc_id", length = 40)
|
||||||
|
private String ckycId;
|
||||||
|
|
||||||
|
@Column(name = "adult_minor", length = 20, nullable = false)
|
||||||
|
private String adultMinor;
|
||||||
|
|
||||||
|
@Column(name = "minor_date")
|
||||||
|
private LocalDate minorDate;
|
||||||
|
|
||||||
|
@Column(name = "guardian_client_id")
|
||||||
|
private Long guardianClientId;
|
||||||
|
|
||||||
|
@Column(name = "min_account_opt", length = 40)
|
||||||
|
private String minAccountOpt;
|
||||||
|
|
||||||
|
@Column(name = "relation", length = 40)
|
||||||
|
private String relation;
|
||||||
|
|
||||||
|
@Column(name = "opening_date", nullable = false)
|
||||||
|
private LocalDate openingDate;
|
||||||
|
|
||||||
|
@Column(name = "months")
|
||||||
|
private Integer month;
|
||||||
|
|
||||||
|
@Column(name = "maturity_date")
|
||||||
|
private LocalDate maturityDate;
|
||||||
|
|
||||||
|
@Column(name = "reopen_date")
|
||||||
|
private LocalDate reopenDate;
|
||||||
|
|
||||||
|
@Column(name = "deposit_amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal depositAmount;
|
||||||
|
|
||||||
|
@Column(name = "daily_deposit_amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal dailyDepositAmount;
|
||||||
|
|
||||||
|
@Column(name = "days", nullable = false)
|
||||||
|
private Integer days;
|
||||||
|
|
||||||
|
@Column(name = "ledger_no", length = 40)
|
||||||
|
private String ledgerNo;
|
||||||
|
|
||||||
|
@Column(name = "maturity_amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal maturityAmount;
|
||||||
|
|
||||||
|
@Column(name = "interest_rate", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal interestRate;
|
||||||
|
|
||||||
|
@Column(name = "withdraw_type", length = 20)
|
||||||
|
private String withdrawType;
|
||||||
|
|
||||||
|
@Column(name = "od1_flag", nullable = false)
|
||||||
|
private boolean od1Flag;
|
||||||
|
|
||||||
|
@Column(name = "od2_flag", nullable = false)
|
||||||
|
private boolean od2Flag;
|
||||||
|
|
||||||
|
@Column(name = "lien", nullable = false)
|
||||||
|
private boolean lien;
|
||||||
|
|
||||||
|
@Column(name = "pay_date")
|
||||||
|
private LocalDate payDate;
|
||||||
|
|
||||||
|
@Column(name = "payment_amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal paymentAmount;
|
||||||
|
|
||||||
|
@Column(name = "current_balance", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal currentBalance;
|
||||||
|
|
||||||
|
@Column(name = "opening_balance", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal openingBalance;
|
||||||
|
|
||||||
|
@Column(name = "is_sms_active", nullable = false)
|
||||||
|
private boolean isSmsActive;
|
||||||
|
|
||||||
|
@Column(name = "is_email_active", nullable = false)
|
||||||
|
private boolean isEmailActive;
|
||||||
|
|
||||||
|
@Column(name = "interest_calc", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal interestCalc;
|
||||||
|
|
||||||
|
@Column(name = "interest_date")
|
||||||
|
private LocalDate interestDate;
|
||||||
|
|
||||||
|
@Column(name = "sp_description", length = 500)
|
||||||
|
private String spDescription;
|
||||||
|
|
||||||
|
@Column(name = "freeze_code", length = 20, nullable = false)
|
||||||
|
private String freezeCode;
|
||||||
|
|
||||||
|
@Column(name = "adv_code", length = 40, nullable = false)
|
||||||
|
private String advCode;
|
||||||
|
|
||||||
|
@Column(name = "credit_to_branch", length = 100)
|
||||||
|
private String creditToBranch;
|
||||||
|
|
||||||
|
@Column(name = "credit_to_account_type", length = 100)
|
||||||
|
private String creditToAccountType;
|
||||||
|
|
||||||
|
@Column(name = "credit_to_account", length = 50)
|
||||||
|
private String creditToAccount;
|
||||||
|
|
||||||
|
@Column(name = "nominee_applicable", nullable = false)
|
||||||
|
private boolean nomineeApplicable;
|
||||||
|
|
||||||
|
@Column(name = "nominee_split_mode", length = 20)
|
||||||
|
private String nomineeSplitMode;
|
||||||
|
|
||||||
|
@Column(name = "status", length = 20, nullable = false)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "close_date")
|
||||||
|
private LocalDate closeDate;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "party", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
|
@OrderBy("id")
|
||||||
|
private List<PartyNominee> nominees = new ArrayList<>();
|
||||||
|
|
||||||
|
protected Party() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
private Party(final Client client, final Agent agent, final String partyCode, final JsonCommand command) {
|
||||||
|
this.client = client;
|
||||||
|
this.agent = agent;
|
||||||
|
this.partyCode = partyCode;
|
||||||
|
this.subType = command.stringValueOfParameterNamed("subType");
|
||||||
|
this.lastAccount = command.stringValueOfParameterNamedAllowingNull("lastAccount");
|
||||||
|
this.nominal = command.booleanPrimitiveValueOfParameterNamed("nominal");
|
||||||
|
this.aadharNo = command.stringValueOfParameterNamedAllowingNull("aadharNo");
|
||||||
|
this.partyName = command.stringValueOfParameterNamed("partyName");
|
||||||
|
this.partyName1 = command.stringValueOfParameterNamedAllowingNull("partyName1");
|
||||||
|
this.address1 = command.stringValueOfParameterNamed("address1");
|
||||||
|
this.address2 = command.stringValueOfParameterNamedAllowingNull("address2");
|
||||||
|
this.address3 = command.stringValueOfParameterNamedAllowingNull("address3");
|
||||||
|
this.city = command.stringValueOfParameterNamed("city");
|
||||||
|
this.pincode = command.stringValueOfParameterNamed("pincode");
|
||||||
|
this.gender = command.stringValueOfParameterNamedAllowingNull("gender");
|
||||||
|
this.panCardNo = command.stringValueOfParameterNamedAllowingNull("panCardNo");
|
||||||
|
this.telephone = command.stringValueOfParameterNamedAllowingNull("telephone");
|
||||||
|
this.mobileNoSms = command.stringValueOfParameterNamed("mobileNoSms");
|
||||||
|
this.operation = command.stringValueOfParameterNamed("operation");
|
||||||
|
this.birthDate = command.localDateValueOfParameterNamed("birthDate");
|
||||||
|
this.occupation = command.stringValueOfParameterNamedAllowingNull("occupation");
|
||||||
|
this.risk = command.stringValueOfParameterNamedAllowingNull("risk");
|
||||||
|
this.ckycId = command.stringValueOfParameterNamedAllowingNull("ckycId");
|
||||||
|
this.adultMinor = command.stringValueOfParameterNamed("adultMinor");
|
||||||
|
this.minorDate = command.localDateValueOfParameterNamed("minorDate");
|
||||||
|
this.guardianClientId = command.longValueOfParameterNamed("guardianClientId");
|
||||||
|
this.minAccountOpt = command.stringValueOfParameterNamedAllowingNull("minAccountOpt");
|
||||||
|
this.relation = command.stringValueOfParameterNamedAllowingNull("relation");
|
||||||
|
this.openingDate = command.localDateValueOfParameterNamed("openingDate");
|
||||||
|
this.month = command.integerValueOfParameterNamedDefaultToNullIfZero("month");
|
||||||
|
this.maturityDate = command.localDateValueOfParameterNamed("maturityDate");
|
||||||
|
this.reopenDate = command.localDateValueOfParameterNamed("reopenDate");
|
||||||
|
this.depositAmount = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("depositAmount"));
|
||||||
|
this.dailyDepositAmount = command.bigDecimalValueOfParameterNamed("dailyDepositAmount");
|
||||||
|
this.days = command.integerValueOfParameterNamed("days");
|
||||||
|
this.ledgerNo = command.stringValueOfParameterNamedAllowingNull("ledgerNo");
|
||||||
|
this.maturityAmount = BigDecimal.ZERO;
|
||||||
|
this.interestRate = command.parameterExists("interestRate")
|
||||||
|
? command.bigDecimalValueOfParameterNamed("interestRate")
|
||||||
|
: DEFAULT_INTEREST_RATE;
|
||||||
|
this.withdrawType = command.stringValueOfParameterNamedAllowingNull("withdrawType");
|
||||||
|
this.od1Flag = false;
|
||||||
|
this.od2Flag = false;
|
||||||
|
this.lien = command.booleanPrimitiveValueOfParameterNamed("lien");
|
||||||
|
this.payDate = command.localDateValueOfParameterNamed("payDate");
|
||||||
|
this.paymentAmount = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("paymentAmount"));
|
||||||
|
this.openingBalance = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("openingBalance"));
|
||||||
|
this.currentBalance = this.openingBalance;
|
||||||
|
this.isSmsActive = command.booleanPrimitiveValueOfParameterNamed("isSmsActive");
|
||||||
|
this.isEmailActive = command.booleanPrimitiveValueOfParameterNamed("isEmailActive");
|
||||||
|
this.interestCalc = BigDecimal.ZERO;
|
||||||
|
this.interestDate = command.localDateValueOfParameterNamed("interestDate");
|
||||||
|
this.spDescription = command.stringValueOfParameterNamedAllowingNull("spDescription");
|
||||||
|
this.freezeCode = StringUtils.defaultIfBlank(command.stringValueOfParameterNamedAllowingNull("freezeCode"), "Normal");
|
||||||
|
this.advCode = StringUtils.defaultIfBlank(command.stringValueOfParameterNamedAllowingNull("advCode"), "None");
|
||||||
|
this.creditToBranch = command.stringValueOfParameterNamedAllowingNull("creditToBranch");
|
||||||
|
this.creditToAccountType = command.stringValueOfParameterNamedAllowingNull("creditToAccountType");
|
||||||
|
this.creditToAccount = command.stringValueOfParameterNamedAllowingNull("creditToAccount");
|
||||||
|
this.nomineeApplicable = command.booleanPrimitiveValueOfParameterNamed("nomineeApplicable");
|
||||||
|
this.nomineeSplitMode = StringUtils.defaultIfBlank(command.stringValueOfParameterNamedAllowingNull("nomineeSplitMode"),
|
||||||
|
"Percentage");
|
||||||
|
this.status = STATUS_ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Party fromJson(final Client client, final Agent agent, final String partyCode, final JsonCommand command) {
|
||||||
|
return new Party(client, agent, partyCode, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal orZero(final BigDecimal value) {
|
||||||
|
return value == null ? BigDecimal.ZERO : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNominee(final PartyNominee nominee) {
|
||||||
|
nominee.setParty(this);
|
||||||
|
this.nominees.add(nominee);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetNominees() {
|
||||||
|
this.nominees.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeClient(final Client newClient) {
|
||||||
|
this.client = newClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeAgent(final Agent newAgent) {
|
||||||
|
this.agent = newAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links this Party to an existing real Savings Account, resolved by account number in the write
|
||||||
|
* service. There is no unlink - once a Party's DDS scheme is tied to a real account, it stays
|
||||||
|
* tied.
|
||||||
|
*/
|
||||||
|
public void linkSavingsAccount(final Long savingsAccountId) {
|
||||||
|
this.savingsAccountId = savingsAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal credit(final BigDecimal amount) {
|
||||||
|
this.currentBalance = this.currentBalance.add(amount);
|
||||||
|
return this.currentBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal debit(final BigDecimal amount) {
|
||||||
|
this.currentBalance = this.currentBalance.subtract(amount);
|
||||||
|
return this.currentBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return STATUS_CLOSED.equals(this.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes this Party's account as of the given date. The balance must already be zero - a Party
|
||||||
|
* is settled to zero (a full Withdraw Deposite/payout) before it is closed, not closed with money
|
||||||
|
* still owed to it.
|
||||||
|
*/
|
||||||
|
public void close(final LocalDate closeDate) {
|
||||||
|
this.status = STATUS_CLOSED;
|
||||||
|
this.closeDate = closeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stamps the date interest (regular or OD) was last calculated up to, so the next batch run
|
||||||
|
* accrues only the days since. Set on POST, not on calculate/preview.
|
||||||
|
*/
|
||||||
|
public void markInterestCalculatedThrough(final LocalDate date) {
|
||||||
|
this.interestDate = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long clientId() {
|
||||||
|
return this.client.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long agentId() {
|
||||||
|
return this.agent.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> update(final JsonCommand command) {
|
||||||
|
|
||||||
|
final Map<String, Object> actualChanges = new LinkedHashMap<>(40);
|
||||||
|
|
||||||
|
if (command.isChangeInLongParameterNamed("clientId", this.client.getId())) {
|
||||||
|
actualChanges.put("clientId", command.longValueOfParameterNamed("clientId"));
|
||||||
|
}
|
||||||
|
if (command.isChangeInLongParameterNamed("agentId", this.agent.getId())) {
|
||||||
|
actualChanges.put("agentId", command.longValueOfParameterNamed("agentId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "subType", this.subType, v -> this.subType = v);
|
||||||
|
stringChange(command, actualChanges, "lastAccount", this.lastAccount, v -> this.lastAccount = v);
|
||||||
|
|
||||||
|
if (command.isChangeInBooleanParameterNamed("nominal", this.nominal)) {
|
||||||
|
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("nominal");
|
||||||
|
actualChanges.put("nominal", newValue);
|
||||||
|
this.nominal = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "aadharNo", this.aadharNo, v -> this.aadharNo = v);
|
||||||
|
stringChange(command, actualChanges, "partyName", this.partyName, v -> this.partyName = v);
|
||||||
|
stringChange(command, actualChanges, "partyName1", this.partyName1, v -> this.partyName1 = v);
|
||||||
|
stringChange(command, actualChanges, "address1", this.address1, v -> this.address1 = v);
|
||||||
|
stringChange(command, actualChanges, "address2", this.address2, v -> this.address2 = v);
|
||||||
|
stringChange(command, actualChanges, "address3", this.address3, v -> this.address3 = v);
|
||||||
|
stringChange(command, actualChanges, "city", this.city, v -> this.city = v);
|
||||||
|
stringChange(command, actualChanges, "pincode", this.pincode, v -> this.pincode = v);
|
||||||
|
stringChange(command, actualChanges, "gender", this.gender, v -> this.gender = v);
|
||||||
|
stringChange(command, actualChanges, "panCardNo", this.panCardNo, v -> this.panCardNo = v);
|
||||||
|
stringChange(command, actualChanges, "telephone", this.telephone, v -> this.telephone = v);
|
||||||
|
stringChange(command, actualChanges, "mobileNoSms", this.mobileNoSms, v -> this.mobileNoSms = v);
|
||||||
|
stringChange(command, actualChanges, "operation", this.operation, v -> this.operation = v);
|
||||||
|
|
||||||
|
if (command.isChangeInDateParameterNamed("birthDate", this.birthDate)) {
|
||||||
|
actualChanges.put("birthDate", command.stringValueOfParameterNamed("birthDate"));
|
||||||
|
this.birthDate = command.localDateValueOfParameterNamed("birthDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "occupation", this.occupation, v -> this.occupation = v);
|
||||||
|
stringChange(command, actualChanges, "risk", this.risk, v -> this.risk = v);
|
||||||
|
stringChange(command, actualChanges, "ckycId", this.ckycId, v -> this.ckycId = v);
|
||||||
|
stringChange(command, actualChanges, "adultMinor", this.adultMinor, v -> this.adultMinor = v);
|
||||||
|
|
||||||
|
if (command.isChangeInDateParameterNamed("minorDate", this.minorDate)) {
|
||||||
|
actualChanges.put("minorDate", command.stringValueOfParameterNamed("minorDate"));
|
||||||
|
this.minorDate = command.localDateValueOfParameterNamed("minorDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInLongParameterNamed("guardianClientId", this.guardianClientId)) {
|
||||||
|
final Long newValue = command.longValueOfParameterNamed("guardianClientId");
|
||||||
|
actualChanges.put("guardianClientId", newValue);
|
||||||
|
this.guardianClientId = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "minAccountOpt", this.minAccountOpt, v -> this.minAccountOpt = v);
|
||||||
|
stringChange(command, actualChanges, "relation", this.relation, v -> this.relation = v);
|
||||||
|
|
||||||
|
if (command.isChangeInDateParameterNamed("openingDate", this.openingDate)) {
|
||||||
|
actualChanges.put("openingDate", command.stringValueOfParameterNamed("openingDate"));
|
||||||
|
this.openingDate = command.localDateValueOfParameterNamed("openingDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInIntegerParameterNamed("month", this.month)) {
|
||||||
|
final Integer newValue = command.integerValueOfParameterNamed("month");
|
||||||
|
actualChanges.put("month", newValue);
|
||||||
|
this.month = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInDateParameterNamed("maturityDate", this.maturityDate)) {
|
||||||
|
actualChanges.put("maturityDate", command.stringValueOfParameterNamed("maturityDate"));
|
||||||
|
this.maturityDate = command.localDateValueOfParameterNamed("maturityDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInDateParameterNamed("reopenDate", this.reopenDate)) {
|
||||||
|
actualChanges.put("reopenDate", command.stringValueOfParameterNamed("reopenDate"));
|
||||||
|
this.reopenDate = command.localDateValueOfParameterNamed("reopenDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("depositAmount", this.depositAmount)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("depositAmount");
|
||||||
|
actualChanges.put("depositAmount", newValue);
|
||||||
|
this.depositAmount = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("dailyDepositAmount", this.dailyDepositAmount)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("dailyDepositAmount");
|
||||||
|
actualChanges.put("dailyDepositAmount", newValue);
|
||||||
|
this.dailyDepositAmount = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInIntegerParameterNamed("days", this.days)) {
|
||||||
|
final Integer newValue = command.integerValueOfParameterNamed("days");
|
||||||
|
actualChanges.put("days", newValue);
|
||||||
|
this.days = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("interestRate", this.interestRate)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("interestRate");
|
||||||
|
actualChanges.put("interestRate", newValue);
|
||||||
|
this.interestRate = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "ledgerNo", this.ledgerNo, v -> this.ledgerNo = v);
|
||||||
|
stringChange(command, actualChanges, "withdrawType", this.withdrawType, v -> this.withdrawType = v);
|
||||||
|
|
||||||
|
if (command.isChangeInBooleanParameterNamed("lien", this.lien)) {
|
||||||
|
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("lien");
|
||||||
|
actualChanges.put("lien", newValue);
|
||||||
|
this.lien = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInDateParameterNamed("payDate", this.payDate)) {
|
||||||
|
actualChanges.put("payDate", command.stringValueOfParameterNamed("payDate"));
|
||||||
|
this.payDate = command.localDateValueOfParameterNamed("payDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("paymentAmount", this.paymentAmount)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("paymentAmount");
|
||||||
|
actualChanges.put("paymentAmount", newValue);
|
||||||
|
this.paymentAmount = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBigDecimalParameterNamed("openingBalance", this.openingBalance)) {
|
||||||
|
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("openingBalance");
|
||||||
|
actualChanges.put("openingBalance", newValue);
|
||||||
|
this.openingBalance = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBooleanParameterNamed("isSmsActive", this.isSmsActive)) {
|
||||||
|
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("isSmsActive");
|
||||||
|
actualChanges.put("isSmsActive", newValue);
|
||||||
|
this.isSmsActive = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInBooleanParameterNamed("isEmailActive", this.isEmailActive)) {
|
||||||
|
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("isEmailActive");
|
||||||
|
actualChanges.put("isEmailActive", newValue);
|
||||||
|
this.isEmailActive = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChangeInDateParameterNamed("interestDate", this.interestDate)) {
|
||||||
|
actualChanges.put("interestDate", command.stringValueOfParameterNamed("interestDate"));
|
||||||
|
this.interestDate = command.localDateValueOfParameterNamed("interestDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "spDescription", this.spDescription, v -> this.spDescription = v);
|
||||||
|
stringChange(command, actualChanges, "freezeCode", this.freezeCode, v -> this.freezeCode = v);
|
||||||
|
stringChange(command, actualChanges, "advCode", this.advCode, v -> this.advCode = v);
|
||||||
|
stringChange(command, actualChanges, "creditToBranch", this.creditToBranch, v -> this.creditToBranch = v);
|
||||||
|
stringChange(command, actualChanges, "creditToAccountType", this.creditToAccountType, v -> this.creditToAccountType = v);
|
||||||
|
stringChange(command, actualChanges, "creditToAccount", this.creditToAccount, v -> this.creditToAccount = v);
|
||||||
|
|
||||||
|
if (command.isChangeInBooleanParameterNamed("nomineeApplicable", this.nomineeApplicable)) {
|
||||||
|
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("nomineeApplicable");
|
||||||
|
actualChanges.put("nomineeApplicable", newValue);
|
||||||
|
this.nomineeApplicable = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stringChange(command, actualChanges, "nomineeSplitMode", this.nomineeSplitMode, v -> this.nomineeSplitMode = v);
|
||||||
|
|
||||||
|
return actualChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stringChange(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
|
||||||
|
final String existingValue, final Consumer<String> setter) {
|
||||||
|
if (command.isChangeInStringParameterNamed(paramName, existingValue)) {
|
||||||
|
final String newValue = command.stringValueOfParameterNamed(paramName);
|
||||||
|
actualChanges.put(paramName, newValue);
|
||||||
|
setter.accept(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 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.dds.party.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 lombok.Getter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single nominee recorded against a {@link Party}. Maker-checker on nominee edits is enforced at
|
||||||
|
* the command layer (the whole Party update goes through the standard maker-checker command flow),
|
||||||
|
* not on this child row individually.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_dds_party_nominee")
|
||||||
|
public class PartyNominee extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "party_id", nullable = false)
|
||||||
|
private Party party;
|
||||||
|
|
||||||
|
@Column(name = "nomination_date", nullable = false)
|
||||||
|
private LocalDate nominationDate;
|
||||||
|
|
||||||
|
@Column(name = "name", length = 100, nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "mobile_no", length = 20)
|
||||||
|
private String mobileNo;
|
||||||
|
|
||||||
|
@Column(name = "birth_date")
|
||||||
|
private LocalDate birthDate;
|
||||||
|
|
||||||
|
@Column(name = "minor", nullable = false)
|
||||||
|
private boolean minor;
|
||||||
|
|
||||||
|
@Column(name = "relation", length = 40, nullable = false)
|
||||||
|
private String relation;
|
||||||
|
|
||||||
|
@Column(name = "gender", length = 20, nullable = false)
|
||||||
|
private String gender;
|
||||||
|
|
||||||
|
@Column(name = "guardian_name", length = 100)
|
||||||
|
private String guardianName;
|
||||||
|
|
||||||
|
protected PartyNominee() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartyNominee(final LocalDate nominationDate, final String name, final String mobileNo, final LocalDate birthDate,
|
||||||
|
final boolean minor, final String relation, final String gender, final String guardianName) {
|
||||||
|
this.nominationDate = nominationDate;
|
||||||
|
this.name = name;
|
||||||
|
this.mobileNo = StringUtils.defaultIfEmpty(mobileNo, null);
|
||||||
|
this.birthDate = birthDate;
|
||||||
|
this.minor = minor;
|
||||||
|
this.relation = relation;
|
||||||
|
this.gender = gender;
|
||||||
|
this.guardianName = StringUtils.defaultIfEmpty(guardianName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setParty(final Party party) {
|
||||||
|
this.party = party;
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.dds.party.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a Close Party command, or a Party Tran collection, targets a Party that is already
|
||||||
|
* closed.
|
||||||
|
*/
|
||||||
|
public class PartyAlreadyClosedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public PartyAlreadyClosedException(final String partyCode) {
|
||||||
|
super("error.msg.party.already.closed", "Party " + partyCode + " is already closed", partyCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.dds.party.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a Close Party command is attempted while the Party's balance is still non-zero - the
|
||||||
|
* remaining balance must be paid out (a Withdraw Deposite to zero) before the account can be closed.
|
||||||
|
*/
|
||||||
|
public class PartyHasBalanceException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public PartyHasBalanceException(final String partyCode) {
|
||||||
|
super("error.msg.party.has.balance", "Party " + partyCode + " still has a non-zero balance and cannot be closed", partyCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.dds.party.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||||
|
import org.springframework.dao.EmptyResultDataAccessException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link RuntimeException} thrown when a DDS party resource is not found.
|
||||||
|
*/
|
||||||
|
public class PartyNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||||
|
|
||||||
|
public PartyNotFoundException(final Long id) {
|
||||||
|
super("error.msg.party.id.invalid", "Party with identifier " + id + " does not exist", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartyNotFoundException(final Long id, final EmptyResultDataAccessException e) {
|
||||||
|
super("error.msg.party.id.invalid", "Party with identifier " + id + " does not exist", id, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartyNotFoundException(final String partyCode) {
|
||||||
|
super("error.msg.party.code.invalid", "Party with code " + partyCode + " does not exist", partyCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -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.dds.party.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the savingsAccountNo given for a Party belongs to a different client than the Party's
|
||||||
|
* own clientId - a Party can only be linked to its own client's savings account.
|
||||||
|
*/
|
||||||
|
public class PartySavingsAccountClientMismatchException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public PartySavingsAccountClientMismatchException(final String savingsAccountNo) {
|
||||||
|
super("error.msg.party.savings.account.client.mismatch",
|
||||||
|
"Savings account " + savingsAccountNo + " does not belong to this party's client", savingsAccountNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.dds.party.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a Party's savingsAccountNo does not match any non-closed Savings Account.
|
||||||
|
*/
|
||||||
|
public class PartySavingsAccountNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||||
|
|
||||||
|
public PartySavingsAccountNotFoundException(final String savingsAccountNo) {
|
||||||
|
super("error.msg.party.savings.account.not.found", "No open savings account found with account number " + savingsAccountNo,
|
||||||
|
savingsAccountNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.dds.report.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic tabular result shape shared by every DDS report - one column list plus one row list, both
|
||||||
|
* keyed the same way, so a single report viewer screen can render any of them without a bespoke
|
||||||
|
* result type per report.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public final class DdsReportData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final String reportKey;
|
||||||
|
private final boolean available;
|
||||||
|
private final String message;
|
||||||
|
private final List<String> columns;
|
||||||
|
private final List<Map<String, Object>> rows;
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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.dds.transaction.data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
public final class PartyTransactionData implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final Long partyId;
|
||||||
|
private final String partyCode;
|
||||||
|
private final String partyName;
|
||||||
|
private final Long agentId;
|
||||||
|
private final String agentCode;
|
||||||
|
private final LocalDate transactionDate;
|
||||||
|
private final String transactionType;
|
||||||
|
private final String direction;
|
||||||
|
private final String transactionMode;
|
||||||
|
private final String accountType;
|
||||||
|
private final String accountNo;
|
||||||
|
private final BigDecimal amount;
|
||||||
|
private final String chequeNo;
|
||||||
|
private final String tokenNo;
|
||||||
|
private final String description;
|
||||||
|
private final BigDecimal balanceAfterTransaction;
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* 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.dds.transaction.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 lombok.Getter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.organisation.agent.domain.Agent;
|
||||||
|
import org.apache.fineract.portfolio.dds.party.domain.Party;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single posted entry on the DDS ledger: a Party's day's collection ("Party Tran", direction
|
||||||
|
* CREDIT), one leg of a "Withdraw Deposite" transfer between two parties (the creditor leg CREDIT,
|
||||||
|
* the debtor leg DEBIT), or an Agent's settlement of collected cash to its branch ("Agent Tran",
|
||||||
|
* direction DEBIT - no {@link Party} involved, so {@code party} is null for this type).
|
||||||
|
* {@code balanceAfterTransaction} holds the Party's balance for Party-linked rows, or the Agent's
|
||||||
|
* balance for an Agent Tran row. Immutable once posted - like any transaction, it is reversed with
|
||||||
|
* an opposing entry, not edited, so there is deliberately no update API for this entity.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_dds_party_transaction")
|
||||||
|
public class PartyTransaction extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "party_id")
|
||||||
|
private Party party;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "agent_id", nullable = false)
|
||||||
|
private Agent agent;
|
||||||
|
|
||||||
|
@Column(name = "transaction_date", nullable = false)
|
||||||
|
private LocalDate transactionDate;
|
||||||
|
|
||||||
|
@Column(name = "transaction_type", length = 30, nullable = false)
|
||||||
|
private String transactionType;
|
||||||
|
|
||||||
|
@Column(name = "direction", length = 10, nullable = false)
|
||||||
|
private String direction;
|
||||||
|
|
||||||
|
@Column(name = "transaction_mode", length = 20)
|
||||||
|
private String transactionMode;
|
||||||
|
|
||||||
|
@Column(name = "account_type", length = 100)
|
||||||
|
private String accountType;
|
||||||
|
|
||||||
|
@Column(name = "account_no", length = 50)
|
||||||
|
private String accountNo;
|
||||||
|
|
||||||
|
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
@Column(name = "cheque_no", length = 40)
|
||||||
|
private String chequeNo;
|
||||||
|
|
||||||
|
@Column(name = "token_no", length = 40)
|
||||||
|
private String tokenNo;
|
||||||
|
|
||||||
|
@Column(name = "description", length = 500)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Column(name = "balance_after_transaction", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal balanceAfterTransaction;
|
||||||
|
|
||||||
|
protected PartyTransaction() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartyTransaction(final Party party, final Agent agent, final LocalDate transactionDate, final String transactionType,
|
||||||
|
final String direction, final String transactionMode, final String accountType, final String accountNo,
|
||||||
|
final BigDecimal amount, final String chequeNo, final String tokenNo, final String description,
|
||||||
|
final BigDecimal balanceAfterTransaction) {
|
||||||
|
this.party = party;
|
||||||
|
this.agent = agent;
|
||||||
|
this.transactionDate = transactionDate;
|
||||||
|
this.transactionType = transactionType;
|
||||||
|
this.direction = direction;
|
||||||
|
this.transactionMode = transactionMode;
|
||||||
|
this.accountType = accountType;
|
||||||
|
this.accountNo = accountNo;
|
||||||
|
this.amount = amount;
|
||||||
|
this.chequeNo = chequeNo;
|
||||||
|
this.tokenNo = tokenNo;
|
||||||
|
this.description = description;
|
||||||
|
this.balanceAfterTransaction = balanceAfterTransaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -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.dds.transaction.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an Agent Tran settlement amount exceeds the Agent's current (collected but not yet
|
||||||
|
* settled) balance.
|
||||||
|
*/
|
||||||
|
public class InsufficientAgentBalanceException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public InsufficientAgentBalanceException(final String agentCode) {
|
||||||
|
super("error.msg.agent.insufficient.balance",
|
||||||
|
"Agent " + agentCode + " does not have sufficient collected balance for this settlement", agentCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user