Compare commits
79
Commits
ea2dfd789d
...
383dfdc6e9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
383dfdc6e9 | ||
|
|
5ed95c27e8 | ||
|
|
fde670e644 | ||
|
|
3bb7fc8050 | ||
|
|
04d65d857f | ||
|
|
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 pull *)",
|
||||
"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": [
|
||||
"c:\\Users\\adity\\Music\\CBS\\cfcfrms-bank-api",
|
||||
|
||||
+40
-37
@@ -1,37 +1,40 @@
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
.gradle
|
||||
build
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
*.swp
|
||||
*.DS_Store
|
||||
.idea
|
||||
.vscode
|
||||
catalina.base_IS_UNDEFINED/
|
||||
keystore.jks
|
||||
bin/
|
||||
.lock
|
||||
fineract-provider/out/
|
||||
fineract-provider/config/swagger/config.json
|
||||
fineract-provider/config/swagger/fineract-input.yaml
|
||||
licenses
|
||||
**/.asciidoctor/images/*
|
||||
**/.asciidoctor/diagram/*
|
||||
**/images/diag-*.svg
|
||||
|
||||
fineract-provider/src/main/generated/
|
||||
|
||||
**/out/
|
||||
gradleExp/
|
||||
|
||||
.run/
|
||||
.java-version
|
||||
|
||||
.windsurf/
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
.gradle
|
||||
build
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
*.swp
|
||||
*.DS_Store
|
||||
.idea
|
||||
.vscode
|
||||
catalina.base_IS_UNDEFINED/
|
||||
keystore.jks
|
||||
bin/
|
||||
.lock
|
||||
fineract-provider/out/
|
||||
fineract-provider/config/swagger/config.json
|
||||
fineract-provider/config/swagger/fineract-input.yaml
|
||||
licenses
|
||||
**/.asciidoctor/images/*
|
||||
**/.asciidoctor/diagram/*
|
||||
**/images/diag-*.svg
|
||||
|
||||
fineract-provider/src/main/generated/
|
||||
|
||||
**/out/
|
||||
gradleExp/
|
||||
|
||||
.run/
|
||||
.java-version
|
||||
|
||||
.windsurf/
|
||||
|
||||
# Application logs
|
||||
fineract-provider/logs/
|
||||
+284
-272
@@ -1,272 +1,284 @@
|
||||
/**
|
||||
* 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.accounting.glaccount.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.accounting.common.AccountingEnumerations;
|
||||
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
|
||||
import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
|
||||
import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidClassificationException;
|
||||
import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException;
|
||||
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
|
||||
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
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 final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static final String SQL_AND = " and ";
|
||||
|
||||
private static final class GLAccountMapper implements RowMapper<GLAccountData> {
|
||||
|
||||
private final JournalEntryAssociationParametersData associationParametersData;
|
||||
|
||||
GLAccountMapper(final JournalEntryAssociationParametersData associationParametersData) {
|
||||
if (associationParametersData == null) {
|
||||
this.associationParametersData = new JournalEntryAssociationParametersData();
|
||||
} else {
|
||||
this.associationParametersData = associationParametersData;
|
||||
}
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
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, ")
|
||||
.append("classification_enum as classification, account_usage as accountUsage, gl.description as description, ")
|
||||
.append(NAME_DECORATED_BASE_ON_HIERARCHY).append(" as nameDecorated, ")
|
||||
.append("cv.id as codeId, cv.code_value as codeValue ");
|
||||
if (this.associationParametersData.isRunningBalanceRequired()) {
|
||||
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 ");
|
||||
if (this.associationParametersData.isRunningBalanceRequired()) {
|
||||
sb.append("left outer Join acc_gl_journal_entry gl_j on gl_j.account_id = gl.id");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String name = rs.getString("name");
|
||||
final Long parentId = JdbcSupport.getLong(rs, "parentId");
|
||||
final String glCode = rs.getString("glCode");
|
||||
final boolean disabled = rs.getBoolean("disabled");
|
||||
final boolean manualEntriesAllowed = rs.getBoolean("manualEntriesAllowed");
|
||||
final int accountTypeId = JdbcSupport.getInteger(rs, "classification");
|
||||
final EnumOptionData accountType = AccountingEnumerations.gLAccountType(accountTypeId);
|
||||
final int usageId = JdbcSupport.getInteger(rs, "accountUsage");
|
||||
final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(usageId);
|
||||
final String description = rs.getString("description");
|
||||
final String nameDecorated = rs.getString("nameDecorated");
|
||||
final Long codeId = rs.wasNull() ? null : rs.getLong("codeId");
|
||||
final String codeValue = rs.getString("codeValue");
|
||||
final CodeValueData tagId = CodeValueData.instance(codeId, codeValue);
|
||||
Long organizationRunningBalance = null;
|
||||
if (associationParametersData.isRunningBalanceRequired()) {
|
||||
organizationRunningBalance = rs.getLong("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) {
|
||||
if (accountClassification != null && !checkValidGLAccountType(accountClassification)) {
|
||||
throw new GLAccountInvalidClassificationException(accountClassification);
|
||||
}
|
||||
|
||||
if (usage != null && !checkValidGLAccountUsage(usage)) {
|
||||
throw new GLAccountInvalidClassificationException(accountClassification);
|
||||
}
|
||||
|
||||
final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
|
||||
String sql = "select " + rm.schema();
|
||||
// append SQL statement for fetching account totals
|
||||
if (associationParametersData != null && associationParametersData.isRunningBalanceRequired()) {
|
||||
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 "
|
||||
+ "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 "
|
||||
+ "group by t2.account_id desc) t1)";
|
||||
}
|
||||
final Object[] parameterArray = new Object[3];
|
||||
int arrayPos = 0;
|
||||
boolean filtersPresent = false;
|
||||
if ((accountClassification != null) || StringUtils.isNotBlank(searchParam) || (usage != null) || (manualTransactionsAllowed != null)
|
||||
|| (disabled != null)) {
|
||||
filtersPresent = true;
|
||||
sql += " where";
|
||||
}
|
||||
|
||||
if (filtersPresent) {
|
||||
boolean firstWhereConditionAdded = false;
|
||||
if (accountClassification != null) {
|
||||
sql += " classification_enum = ?";
|
||||
parameterArray[arrayPos] = accountClassification.shortValue();
|
||||
arrayPos = arrayPos + 1;
|
||||
firstWhereConditionAdded = true;
|
||||
}
|
||||
if (StringUtils.isNotBlank(searchParam)) {
|
||||
if (firstWhereConditionAdded) {
|
||||
sql += SQL_AND;
|
||||
}
|
||||
sql += " ( name like %?% or gl_code like %?% )";
|
||||
parameterArray[arrayPos] = searchParam;
|
||||
arrayPos = arrayPos + 1;
|
||||
parameterArray[arrayPos] = searchParam;
|
||||
arrayPos = arrayPos + 1;
|
||||
firstWhereConditionAdded = true;
|
||||
}
|
||||
if (usage != null) {
|
||||
if (firstWhereConditionAdded) {
|
||||
sql += SQL_AND;
|
||||
}
|
||||
if (GLAccountUsage.HEADER.getValue().equals(usage)) {
|
||||
sql += " account_usage = 2 ";
|
||||
} else if (GLAccountUsage.DETAIL.getValue().equals(usage)) {
|
||||
sql += " account_usage = 1 ";
|
||||
}
|
||||
firstWhereConditionAdded = true;
|
||||
}
|
||||
if (manualTransactionsAllowed != null) {
|
||||
if (firstWhereConditionAdded) {
|
||||
sql += SQL_AND;
|
||||
}
|
||||
|
||||
sql += " manual_journal_entries_allowed = " + manualTransactionsAllowed;
|
||||
firstWhereConditionAdded = true;
|
||||
}
|
||||
if (disabled != null) {
|
||||
if (firstWhereConditionAdded) {
|
||||
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
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLAccountData retrieveGLAccountById(final long glAccountId, JournalEntryAssociationParametersData associationParametersData) {
|
||||
try {
|
||||
final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
|
||||
final StringBuilder sql = new StringBuilder();
|
||||
sql.append("select ").append(rm.schema());
|
||||
sql.append(" where gl.id = ?");
|
||||
if (associationParametersData.isRunningBalanceRequired()) {
|
||||
sql.append(" and gl_j.is_running_balance_calculated = true ")
|
||||
.append(" ORDER BY gl_j.entry_date DESC,gl_j.id DESC LIMIT 1");
|
||||
}
|
||||
|
||||
return this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new GLAccountNotFoundException(glAccountId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts(final GLAccountType accountType) {
|
||||
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.DETAIL.getValue(), null, false,
|
||||
new JournalEntryAssociationParametersData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts() {
|
||||
return retrieveAllGLAccounts(null, null, GLAccountUsage.DETAIL.getValue(), null, false,
|
||||
new JournalEntryAssociationParametersData());
|
||||
}
|
||||
|
||||
private static boolean checkValidGLAccountType(final int type) {
|
||||
for (final GLAccountType accountType : GLAccountType.values()) {
|
||||
if (accountType.getValue().equals(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean checkValidGLAccountUsage(final int type) {
|
||||
for (final GLAccountUsage accountUsage : GLAccountUsage.values()) {
|
||||
if (accountUsage.getValue().equals(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLAccountData retrieveNewGLAccountDetails(final Integer type) {
|
||||
return GLAccountData.sensibleDefaultsForNewGLAccountCreation(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(final GLAccountType accountType) {
|
||||
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.HEADER.getValue(), null, false,
|
||||
new JournalEntryAssociationParametersData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType) {
|
||||
final GLAccountDataLookUpMapper mapper = new GLAccountDataLookUpMapper();
|
||||
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
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.accounting.glaccount.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.accounting.common.AccountingEnumerations;
|
||||
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
|
||||
import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
|
||||
import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidClassificationException;
|
||||
import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException;
|
||||
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
|
||||
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
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 final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static final String SQL_AND = " and ";
|
||||
|
||||
private static final class GLAccountMapper implements RowMapper<GLAccountData> {
|
||||
|
||||
private final JournalEntryAssociationParametersData associationParametersData;
|
||||
|
||||
GLAccountMapper(final JournalEntryAssociationParametersData associationParametersData) {
|
||||
if (associationParametersData == null) {
|
||||
this.associationParametersData = new JournalEntryAssociationParametersData();
|
||||
} else {
|
||||
this.associationParametersData = associationParametersData;
|
||||
}
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
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, ")
|
||||
.append("classification_enum as classification, account_usage as accountUsage, gl.description as description, ")
|
||||
.append(NAME_DECORATED_BASE_ON_HIERARCHY).append(" as nameDecorated, ")
|
||||
.append("cv.id as codeId, cv.code_value as codeValue ");
|
||||
if (this.associationParametersData.isRunningBalanceRequired()) {
|
||||
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 ");
|
||||
if (this.associationParametersData.isRunningBalanceRequired()) {
|
||||
sb.append("left outer join acc_gl_journal_entry gl_j on gl_j.id = (")
|
||||
.append("select id from acc_gl_journal_entry ")
|
||||
.append("where account_id = gl.id ")
|
||||
.append("order by id desc limit 1) ");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String name = rs.getString("name");
|
||||
final Long parentId = JdbcSupport.getLong(rs, "parentId");
|
||||
final String glCode = rs.getString("glCode");
|
||||
final boolean disabled = rs.getBoolean("disabled");
|
||||
final boolean manualEntriesAllowed = rs.getBoolean("manualEntriesAllowed");
|
||||
final int accountTypeId = JdbcSupport.getInteger(rs, "classification");
|
||||
final EnumOptionData accountType = AccountingEnumerations.gLAccountType(accountTypeId);
|
||||
final int usageId = JdbcSupport.getInteger(rs, "accountUsage");
|
||||
final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(usageId);
|
||||
final String description = rs.getString("description");
|
||||
final String nameDecorated = rs.getString("nameDecorated");
|
||||
final Long codeId = rs.wasNull() ? null : rs.getLong("codeId");
|
||||
final String codeValue = rs.getString("codeValue");
|
||||
final CodeValueData tagId = CodeValueData.instance(codeId, codeValue);
|
||||
Long organizationRunningBalance = null;
|
||||
if (associationParametersData.isRunningBalanceRequired()) {
|
||||
organizationRunningBalance = rs.getLong("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) {
|
||||
if (accountClassification != null && !checkValidGLAccountType(accountClassification)) {
|
||||
throw new GLAccountInvalidClassificationException(accountClassification);
|
||||
}
|
||||
|
||||
if (usage != null && !checkValidGLAccountUsage(usage)) {
|
||||
throw new GLAccountInvalidClassificationException(accountClassification);
|
||||
}
|
||||
|
||||
final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
|
||||
String sql = "select " + rm.schema();
|
||||
// append SQL statement for fetching account totals
|
||||
if (associationParametersData != null && associationParametersData.isRunningBalanceRequired()) {
|
||||
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 "
|
||||
+ "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 "
|
||||
+ "group by t2.account_id desc) t1)";
|
||||
}
|
||||
final Object[] parameterArray = new Object[3];
|
||||
int arrayPos = 0;
|
||||
boolean filtersPresent = false;
|
||||
if ((accountClassification != null) || StringUtils.isNotBlank(searchParam) || (usage != null) || (manualTransactionsAllowed != null)
|
||||
|| (disabled != null)) {
|
||||
filtersPresent = true;
|
||||
sql += " where";
|
||||
}
|
||||
|
||||
if (filtersPresent) {
|
||||
boolean firstWhereConditionAdded = false;
|
||||
if (accountClassification != null) {
|
||||
sql += " classification_enum = ?";
|
||||
parameterArray[arrayPos] = accountClassification.shortValue();
|
||||
arrayPos = arrayPos + 1;
|
||||
firstWhereConditionAdded = true;
|
||||
}
|
||||
if (StringUtils.isNotBlank(searchParam)) {
|
||||
if (firstWhereConditionAdded) {
|
||||
sql += SQL_AND;
|
||||
}
|
||||
sql += " ( name like ? or gl_code like ? )";
|
||||
parameterArray[arrayPos] = "%" + searchParam + "%";
|
||||
arrayPos = arrayPos + 1;
|
||||
parameterArray[arrayPos] = "%" + searchParam + "%";
|
||||
arrayPos = arrayPos + 1;
|
||||
firstWhereConditionAdded = true;
|
||||
}
|
||||
if (usage != null) {
|
||||
if (firstWhereConditionAdded) {
|
||||
sql += SQL_AND;
|
||||
}
|
||||
if (GLAccountUsage.HEADER.getValue().equals(usage)) {
|
||||
sql += " account_usage = 2 ";
|
||||
} else if (GLAccountUsage.DETAIL.getValue().equals(usage)) {
|
||||
sql += " account_usage = 1 ";
|
||||
}
|
||||
firstWhereConditionAdded = true;
|
||||
}
|
||||
if (manualTransactionsAllowed != null) {
|
||||
if (firstWhereConditionAdded) {
|
||||
sql += SQL_AND;
|
||||
}
|
||||
|
||||
sql += " manual_journal_entries_allowed = " + manualTransactionsAllowed;
|
||||
firstWhereConditionAdded = true;
|
||||
}
|
||||
if (disabled != null) {
|
||||
if (firstWhereConditionAdded) {
|
||||
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
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLAccountData retrieveGLAccountById(final long glAccountId, JournalEntryAssociationParametersData associationParametersData) {
|
||||
try {
|
||||
final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
|
||||
final StringBuilder sql = new StringBuilder();
|
||||
sql.append("select ").append(rm.schema());
|
||||
sql.append(" where gl.id = ?");
|
||||
|
||||
final GLAccountData glAccount = this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
|
||||
|
||||
// Live balance: sum all non-reversed journal entries so cashier sees real-time balance
|
||||
// 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 "
|
||||
+ "WHERE account_id = ? AND (reversed = false OR reversed IS NULL)";
|
||||
final java.math.BigDecimal liveBalance = this.jdbcTemplate.queryForObject(liveBalanceSql,
|
||||
java.math.BigDecimal.class, glAccountId);
|
||||
glAccount.setOrganizationRunningBalance(liveBalance != null ? liveBalance.longValue() : 0L);
|
||||
}
|
||||
|
||||
return glAccount;
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new GLAccountNotFoundException(glAccountId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts(final GLAccountType accountType) {
|
||||
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.DETAIL.getValue(), null, false,
|
||||
new JournalEntryAssociationParametersData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts() {
|
||||
return retrieveAllGLAccounts(null, null, GLAccountUsage.DETAIL.getValue(), null, false,
|
||||
new JournalEntryAssociationParametersData());
|
||||
}
|
||||
|
||||
private static boolean checkValidGLAccountType(final int type) {
|
||||
for (final GLAccountType accountType : GLAccountType.values()) {
|
||||
if (accountType.getValue().equals(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean checkValidGLAccountUsage(final int type) {
|
||||
for (final GLAccountUsage accountUsage : GLAccountUsage.values()) {
|
||||
if (accountUsage.getValue().equals(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLAccountData retrieveNewGLAccountDetails(final Integer type) {
|
||||
return GLAccountData.sensibleDefaultsForNewGLAccountCreation(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(final GLAccountType accountType) {
|
||||
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.HEADER.getValue(), null, false,
|
||||
new JournalEntryAssociationParametersData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType) {
|
||||
final GLAccountDataLookUpMapper mapper = new GLAccountDataLookUpMapper();
|
||||
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
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -76,6 +76,9 @@ public class Teller extends AbstractPersistableCustom<Long> {
|
||||
@Column(name = "state", nullable = false)
|
||||
private Integer status;
|
||||
|
||||
@Column(name = "is_deleted", nullable = false)
|
||||
private boolean deleted = false;
|
||||
|
||||
@OneToMany(mappedBy = "teller", fetch = FetchType.LAZY)
|
||||
private Set<Cashier> cashiers;
|
||||
|
||||
@@ -161,4 +164,12 @@ public class Teller extends AbstractPersistableCustom<Long> {
|
||||
this.office.getId();
|
||||
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> {
|
||||
|
||||
Collection<Teller> findTellerByOfficeId(Long officeId);
|
||||
|
||||
long countByDeletedFalse();
|
||||
}
|
||||
|
||||
+7
@@ -36,6 +36,9 @@ public class TellerRepositoryWrapper {
|
||||
@Transactional(readOnly = true)
|
||||
public Teller findOneWithNotFoundDetection(final Long id) {
|
||||
final Teller teller = this.repository.findById(id).orElseThrow(() -> new TellerNotFoundException(id));
|
||||
if (teller.isDeleted()) {
|
||||
throw new TellerNotFoundException(id);
|
||||
}
|
||||
teller.initializeLazyCollections();
|
||||
return teller;
|
||||
}
|
||||
@@ -51,4 +54,8 @@ public class TellerRepositoryWrapper {
|
||||
public void delete(final Teller teller) {
|
||||
this.repository.delete(teller);
|
||||
}
|
||||
|
||||
public boolean existsAny() {
|
||||
return this.repository.countByDeletedFalse() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.organisation.teller.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class TellerAlreadyExistsException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
private static final String ERROR_MESSAGE_CODE = "error.msg.teller.already.exists";
|
||||
private static final String DEFAULT_ERROR_MESSAGE = "Cannot create teller as a teller is already present.";
|
||||
|
||||
public TellerAlreadyExistsException() {
|
||||
super(ERROR_MESSAGE_CODE, DEFAULT_ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -34,7 +34,8 @@ public enum CommandProcessingResultType {
|
||||
AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
|
||||
REJECTED(3, "commandProcessingResultType.rejected"), //
|
||||
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())
|
||||
.collect(Collectors.toMap(CommandProcessingResultType::getValue, v -> v));
|
||||
@@ -59,4 +60,8 @@ public enum CommandProcessingResultType {
|
||||
public boolean isRejected() {
|
||||
return this == REJECTED;
|
||||
}
|
||||
|
||||
public boolean isTerminate() {
|
||||
return this == TERMINATE;
|
||||
}
|
||||
}
|
||||
|
||||
+169
-1
@@ -71,6 +71,166 @@ public class CommandWrapperBuilder {
|
||||
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;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateCreditBureau() {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "ORGANISATIONCREDITBUREAU";
|
||||
@@ -1611,6 +1771,14 @@ public class CommandWrapperBuilder {
|
||||
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) {
|
||||
this.actionName = "APPROVE";
|
||||
this.entityName = "GSIMACCOUNT";
|
||||
@@ -3565,7 +3733,7 @@ public class CommandWrapperBuilder {
|
||||
this.actionName = "ACTIVATE";
|
||||
this.entityName = "CURRENTACCOUNT";
|
||||
this.savingsId = accountId;
|
||||
this.entityId = null;
|
||||
this.entityId = accountId;
|
||||
this.href = "/currentaccounts/" + accountId + "?command=activate";
|
||||
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.PROCESSED;
|
||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.TERMINATE;
|
||||
import static org.apache.http.HttpStatus.SC_OK;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
@@ -181,7 +182,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
currentSource.setResultStatusCode(SC_OK);
|
||||
currentSource.updateForAudit(result);
|
||||
currentSource.setResult(toApiResultJsonSerializer.serializeResult(result));
|
||||
currentSource.setStatus(PROCESSED);
|
||||
currentSource.setStatus("TERMINATE".equals(wrapper.getActionName()) ? TERMINATE : PROCESSED);
|
||||
|
||||
// Return saved command source
|
||||
return commandSourceService.saveResultSameTransaction(currentSource);
|
||||
|
||||
+150
-116
@@ -1,116 +1,150 @@
|
||||
/**
|
||||
* 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.core.serialization;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.MonthDay;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.infrastructure.core.api.DateAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.ExternalIdAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.LocalDateAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.LocalDateTimeAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.LocalTimeAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.OffsetDateTimeAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy;
|
||||
import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Helper class for serialization of Java objects into JSON using Google's GSON.
|
||||
*/
|
||||
@Service
|
||||
public final class GoogleGsonSerializerHelper {
|
||||
|
||||
public Gson createGsonBuilderForPartialResponseFiltering(final Set<String> responseParameters) {
|
||||
final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters);
|
||||
|
||||
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
|
||||
registerTypeAdapters(builder);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set<String> supportedParameters,
|
||||
final Set<String> responseParameters) {
|
||||
|
||||
final Set<String> parameterNamesToSkip = new HashSet<>();
|
||||
|
||||
if (!responseParameters.isEmpty()) {
|
||||
// strip out all known support parameters from expected response to
|
||||
// see if unsupported parameters requested for response.
|
||||
final Set<String> differentParametersDetectedSet = new HashSet<>(responseParameters);
|
||||
differentParametersDetectedSet.removeAll(supportedParameters);
|
||||
|
||||
if (!differentParametersDetectedSet.isEmpty()) {
|
||||
throw new UnsupportedParameterException(new ArrayList<>(differentParametersDetectedSet));
|
||||
}
|
||||
|
||||
parameterNamesToSkip.addAll(supportedParameters);
|
||||
parameterNamesToSkip.removeAll(responseParameters);
|
||||
}
|
||||
|
||||
final ExclusionStrategy strategy = new ParameterListExclusionStrategy(parameterNamesToSkip);
|
||||
|
||||
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
|
||||
registerTypeAdapters(builder);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public String serializedJsonFrom(final Gson serializer, final Object[] dataObjects) {
|
||||
return serializer.toJson(dataObjects);
|
||||
}
|
||||
|
||||
public String serializedJsonFrom(final Gson serializer, final Object singleDataObject) {
|
||||
return serializer.toJson(singleDataObject);
|
||||
}
|
||||
|
||||
public static Gson createSimpleGson() {
|
||||
return createGsonBuilder().create();
|
||||
}
|
||||
|
||||
public static GsonBuilder createGsonBuilder() {
|
||||
final GsonBuilder builder = new GsonBuilder();
|
||||
registerTypeAdapters(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static void registerTypeAdapters(final GsonBuilder builder) {
|
||||
builder.registerTypeAdapter(java.util.Date.class, new DateAdapter());
|
||||
builder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter());
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.core.serialization;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.MonthDay;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.infrastructure.core.api.DateAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.ExternalIdAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.LocalDateAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.LocalDateTimeAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.LocalTimeAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.OffsetDateTimeAdapter;
|
||||
import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy;
|
||||
import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Helper class for serialization of Java objects into JSON using Google's GSON.
|
||||
*/
|
||||
@Service
|
||||
public final class GoogleGsonSerializerHelper {
|
||||
|
||||
public Gson createGsonBuilderForPartialResponseFiltering(final Set<String> responseParameters) {
|
||||
final ExclusionStrategy strategy = new ParameterListInclusionStrategy(responseParameters);
|
||||
|
||||
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
|
||||
registerTypeAdapters(builder);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public Gson createGsonBuilderWithParameterExclusionSerializationStrategy(final Set<String> supportedParameters,
|
||||
final Set<String> responseParameters) {
|
||||
|
||||
final Set<String> parameterNamesToSkip = new HashSet<>();
|
||||
|
||||
if (!responseParameters.isEmpty()) {
|
||||
// strip out all known support parameters from expected response to
|
||||
// see if unsupported parameters requested for response.
|
||||
final Set<String> differentParametersDetectedSet = new HashSet<>(responseParameters);
|
||||
differentParametersDetectedSet.removeAll(supportedParameters);
|
||||
|
||||
if (!differentParametersDetectedSet.isEmpty()) {
|
||||
throw new UnsupportedParameterException(new ArrayList<>(differentParametersDetectedSet));
|
||||
}
|
||||
|
||||
parameterNamesToSkip.addAll(supportedParameters);
|
||||
parameterNamesToSkip.removeAll(responseParameters);
|
||||
}
|
||||
|
||||
final ExclusionStrategy strategy = new ParameterListExclusionStrategy(parameterNamesToSkip);
|
||||
|
||||
final GsonBuilder builder = new GsonBuilder().addSerializationExclusionStrategy(strategy);
|
||||
registerTypeAdapters(builder);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public String serializedJsonFrom(final Gson serializer, final Object[] dataObjects) {
|
||||
return serializer.toJson(dataObjects);
|
||||
}
|
||||
|
||||
public String serializedJsonFrom(final Gson serializer, final Object singleDataObject) {
|
||||
return serializer.toJson(singleDataObject);
|
||||
}
|
||||
|
||||
public static Gson createSimpleGson() {
|
||||
return createGsonBuilder().create();
|
||||
}
|
||||
|
||||
public static GsonBuilder createGsonBuilder() {
|
||||
final GsonBuilder builder = new GsonBuilder();
|
||||
registerTypeAdapters(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static void registerTypeAdapters(final GsonBuilder builder) {
|
||||
builder.registerTypeAdapter(java.util.Date.class, new DateAdapter());
|
||||
builder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter());
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -61,6 +61,9 @@ public enum JobName {
|
||||
ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Savings"), //
|
||||
JOURNAL_ENTRY_AGGREGATION("Journal Entry Aggregation"), //
|
||||
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"), //
|
||||
; //
|
||||
|
||||
private final String name;
|
||||
|
||||
+79
-71
@@ -1,71 +1,79 @@
|
||||
/**
|
||||
* 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.account;
|
||||
|
||||
public enum PortfolioAccountType {
|
||||
|
||||
INVALID(0, "accountType.invalid"), //
|
||||
LOAN(1, "accountType.loan"), //
|
||||
SAVINGS(2, "accountType.savings"), //
|
||||
;
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
|
||||
PortfolioAccountType(final Integer value, final String code) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static PortfolioAccountType fromInt(final Integer type) {
|
||||
|
||||
PortfolioAccountType enumType = INVALID;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
enumType = LOAN;
|
||||
break;
|
||||
case 2:
|
||||
enumType = SAVINGS;
|
||||
break;
|
||||
default:
|
||||
enumType = INVALID;
|
||||
}
|
||||
}
|
||||
return enumType;
|
||||
}
|
||||
|
||||
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
||||
public boolean isSavingsAccount() {
|
||||
return this.equals(SAVINGS);
|
||||
}
|
||||
|
||||
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
||||
public boolean isLoanAccount() {
|
||||
return this.equals(LOAN);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account;
|
||||
|
||||
public enum PortfolioAccountType {
|
||||
|
||||
INVALID(0, "accountType.invalid"), //
|
||||
LOAN(1, "accountType.loan"), //
|
||||
SAVINGS(2, "accountType.savings"), //
|
||||
CURRENT(3, "accountType.current"), //
|
||||
;
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
|
||||
PortfolioAccountType(final Integer value, final String code) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static PortfolioAccountType fromInt(final Integer type) {
|
||||
|
||||
PortfolioAccountType enumType = INVALID;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
enumType = LOAN;
|
||||
break;
|
||||
case 2:
|
||||
enumType = SAVINGS;
|
||||
break;
|
||||
case 3:
|
||||
enumType = CURRENT;
|
||||
break;
|
||||
default:
|
||||
enumType = INVALID;
|
||||
}
|
||||
}
|
||||
return enumType;
|
||||
}
|
||||
|
||||
// TODO: bad practice and unnecessary code! why not just use the enum values themselves!?!
|
||||
public boolean isSavingsAccount() {
|
||||
return this.equals(SAVINGS);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -224,7 +224,7 @@ public final class DepositsApiConstants {
|
||||
* Deposit Product Parameters
|
||||
*/
|
||||
private static final Set<String> DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
|
||||
monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
|
||||
dateFormatParamName, monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
|
||||
digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName,
|
||||
interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
|
||||
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, accountingRuleParamName, chargesParamName,
|
||||
@@ -512,6 +512,11 @@ public final class DepositsApiConstants {
|
||||
fixedDepositInterestCalculationData.add(tenureInMonthsParamName);
|
||||
fixedDepositInterestCalculationData.add(interestPostingPeriodInMonthsParamName);
|
||||
fixedDepositInterestCalculationData.add(interestCompoundingPeriodInMonthsParamName);
|
||||
// Exact calendar-based calculation params
|
||||
fixedDepositInterestCalculationData.add("submittedOnDate");
|
||||
fixedDepositInterestCalculationData.add("dateFormat");
|
||||
fixedDepositInterestCalculationData.add("locale");
|
||||
fixedDepositInterestCalculationData.add("daysInYear");
|
||||
|
||||
return fixedDepositInterestCalculationData;
|
||||
|
||||
|
||||
+1
@@ -170,6 +170,7 @@ public final class SavingsAccountData implements Serializable {
|
||||
private Boolean balanceSwapOption;
|
||||
private Boolean msme;
|
||||
private Boolean autoDebitFacility;
|
||||
private Boolean terminateTrue;
|
||||
private String avgBalanceCalcPeriod;
|
||||
private String validForPeriod;
|
||||
private Boolean odAccountClosedZeroBalanceInDayClosing;
|
||||
|
||||
+720
-714
File diff suppressed because it is too large
Load Diff
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Read-side view of a single pending Savings Product edit row (m_savings_product_temp), for the Approvals ->
|
||||
* Pending Savings Product Edits screen.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class SavingsProductTempData implements Serializable {
|
||||
|
||||
private Long id;
|
||||
private Long savingsProductId;
|
||||
private String savingsProductName;
|
||||
private Long makerId;
|
||||
private String makerUsername;
|
||||
private Long checkerId;
|
||||
private String checkerUsername;
|
||||
private LocalDateTime createdOn;
|
||||
private LocalDateTime checkedOn;
|
||||
private String status;
|
||||
private String rejectReason;
|
||||
private Map<String, Object> proposedChanges;
|
||||
|
||||
public SavingsProductTempData() {}
|
||||
}
|
||||
+7
-1
@@ -36,7 +36,8 @@ public enum SavingsAccountStatusType {
|
||||
REJECTED(500, "savingsAccountStatusType.rejected"), //
|
||||
CLOSED(600, "savingsAccountStatusType.closed"), //
|
||||
PRE_MATURE_CLOSURE(700, "savingsAccountStatusType.pre.mature.closure"), //
|
||||
MATURED(800, "savingsAccountStatusType.matured"); //
|
||||
MATURED(800, "savingsAccountStatusType.matured"), //
|
||||
TERMINATED(900, "savingsAccountStatusType.terminated"); //
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
@@ -53,6 +54,7 @@ public enum SavingsAccountStatusType {
|
||||
case 600 -> SavingsAccountStatusType.CLOSED;
|
||||
case 700 -> SavingsAccountStatusType.PRE_MATURE_CLOSURE;
|
||||
case 800 -> SavingsAccountStatusType.MATURED;
|
||||
case 900 -> SavingsAccountStatusType.TERMINATED;
|
||||
default -> SavingsAccountStatusType.INVALID;
|
||||
};
|
||||
}
|
||||
@@ -113,4 +115,8 @@ public enum SavingsAccountStatusType {
|
||||
public boolean isPreMatureClosure() {
|
||||
return this.value.equals(SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue());
|
||||
}
|
||||
|
||||
public boolean isTerminated() {
|
||||
return this.value.equals(SavingsAccountStatusType.TERMINATED.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -285,6 +285,11 @@ public final class SavingsEnumerations {
|
||||
SavingsAccountStatusType.MATURED.getCode(), "Matured", submittedAndPendingApproval, isApproved, isRejected,
|
||||
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
|
||||
break;
|
||||
case TERMINATED:
|
||||
optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.TERMINATED.getValue().longValue(),
|
||||
SavingsAccountStatusType.TERMINATED.getCode(), "Terminated", submittedAndPendingApproval, isApproved, isRejected,
|
||||
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
|
||||
break;
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
+52
-5
@@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_current_account", uniqueConstraints = {
|
||||
@@ -102,6 +103,30 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
||||
@Column(name = "closedon_date")
|
||||
private LocalDate closedOnDate;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "submittedon_userid")
|
||||
private AppUser submittedOnByUser;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "approvedon_userid")
|
||||
private AppUser approvedOnByUser;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "activatedon_userid")
|
||||
private AppUser activatedOnByUser;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "rejectedon_userid")
|
||||
private AppUser rejectedOnByUser;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "withdrawnon_userid")
|
||||
private AppUser withdrawnOnByUser;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "closedon_userid")
|
||||
private AppUser closedOnByUser;
|
||||
|
||||
// Currency (copied from product at account creation)
|
||||
@Column(name = "currency_code", nullable = false, length = 3)
|
||||
private String currencyCode;
|
||||
@@ -304,6 +329,12 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
||||
@Column(name = "od_account_closed_zero_balance_in_day_closing")
|
||||
private boolean odAccountClosedZeroBalanceInDayClosing = false;
|
||||
|
||||
@Column(name = "is_linking_enable", nullable = false)
|
||||
private boolean shareLinkingEnabled = false;
|
||||
|
||||
@Column(name = "terminate_true", nullable = false, columnDefinition = "boolean default false")
|
||||
private boolean terminateTrue = false;
|
||||
|
||||
|
||||
// Relationships
|
||||
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@@ -373,29 +404,34 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
||||
|
||||
public void updateAccountNo(String accountNo) { this.accountNo = accountNo; }
|
||||
|
||||
public void approve(LocalDate approvedOnDate) {
|
||||
public void approve(LocalDate approvedOnDate, AppUser approvedOnByUser) {
|
||||
this.statusEnum = 200;
|
||||
this.approvedOnDate = approvedOnDate;
|
||||
this.approvedOnByUser = approvedOnByUser;
|
||||
}
|
||||
|
||||
public void activate(LocalDate activatedOnDate) {
|
||||
public void activate(LocalDate activatedOnDate, AppUser activatedOnByUser) {
|
||||
this.statusEnum = 300;
|
||||
this.activatedOnDate = activatedOnDate;
|
||||
this.activatedOnByUser = activatedOnByUser;
|
||||
}
|
||||
|
||||
public void reject(LocalDate rejectedOnDate) {
|
||||
public void reject(LocalDate rejectedOnDate, AppUser rejectedOnByUser) {
|
||||
this.statusEnum = 500;
|
||||
this.rejectedOnDate = rejectedOnDate;
|
||||
this.rejectedOnByUser = rejectedOnByUser;
|
||||
}
|
||||
|
||||
public void withdraw(LocalDate withdrawnOnDate) {
|
||||
public void withdraw(LocalDate withdrawnOnDate, AppUser withdrawnOnByUser) {
|
||||
this.statusEnum = 400;
|
||||
this.withdrawnOnDate = withdrawnOnDate;
|
||||
this.withdrawnOnByUser = withdrawnOnByUser;
|
||||
}
|
||||
|
||||
public void close(LocalDate closedOnDate) {
|
||||
public void close(LocalDate closedOnDate, AppUser closedOnByUser) {
|
||||
this.statusEnum = 600;
|
||||
this.closedOnDate = closedOnDate;
|
||||
this.closedOnByUser = closedOnByUser;
|
||||
}
|
||||
|
||||
public void setMinRequiredOpeningBalance(java.math.BigDecimal val) { this.minRequiredOpeningBalance = val; }
|
||||
@@ -406,4 +442,15 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
||||
public void setNominalAnnualInterestRateOverdraft(java.math.BigDecimal val) { this.nominalAnnualInterestRateOverdraft = val; }
|
||||
public void setWithdrawalFeeForTransfer(boolean val) { this.withdrawalFeeForTransfer = val; }
|
||||
public void setMinOverdraftForInterestCalculation(java.math.BigDecimal val) { this.minOverdraftForInterestCalculation = val; }
|
||||
|
||||
public boolean isShareLinkingEnabled() { return shareLinkingEnabled; }
|
||||
public void setShareLinkingEnabled(boolean shareLinkingEnabled) { this.shareLinkingEnabled = shareLinkingEnabled; }
|
||||
|
||||
public boolean isTerminateTrue() { return terminateTrue; }
|
||||
public void setTerminateTrue(boolean terminateTrue) { this.terminateTrue = terminateTrue; }
|
||||
|
||||
public void terminate() {
|
||||
this.terminateTrue = true;
|
||||
this.statusEnum = 900;
|
||||
}
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.currentaccount.domain;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Wrapper for {@link CurrentAccountRepository} that is responsible for checking if {@link CurrentAccount} is
|
||||
* returned when using <code>findOne</code> repository method and throwing an appropriate not found exception.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This is to avoid need for checking and throwing in multiple areas of code base where
|
||||
* {@link CurrentAccountRepository} is required.
|
||||
* </p>
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CurrentAccountRepositoryWrapper {
|
||||
|
||||
private final CurrentAccountRepository repository;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public CurrentAccount findOneWithNotFoundDetection(final Long id) {
|
||||
return this.repository.findById(id).orElseThrow(() -> new CurrentAccountNotFoundException(id));
|
||||
}
|
||||
}
|
||||
+31
-10
@@ -37,6 +37,31 @@ public class CurrentAccountNumberGenerator {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private String getNextSequence() {
|
||||
final java.util.List<String> accountNos = this.jdbcTemplate.query(
|
||||
"SELECT account_no FROM m_current_account WHERE account_no IS NOT NULL",
|
||||
(rs, rowNum) -> rs.getString(1));
|
||||
|
||||
long maxVal = 0;
|
||||
for (String accNo : accountNos) {
|
||||
if (accNo != null && accNo.length() >= 5) {
|
||||
String suffix = accNo.substring(accNo.length() - 5);
|
||||
try {
|
||||
long val = Long.parseLong(suffix);
|
||||
if (val > maxVal) {
|
||||
maxVal = val;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
String nextSeq = StringUtils.leftPad(String.valueOf(maxVal + 1), 5, '0');
|
||||
if (nextSeq.length() > 5) {
|
||||
nextSeq = nextSeq.substring(nextSeq.length() - 5);
|
||||
}
|
||||
return nextSeq;
|
||||
}
|
||||
|
||||
public String generate(CurrentAccount account) {
|
||||
Long productId = account.getProductId();
|
||||
|
||||
@@ -59,11 +84,10 @@ public class CurrentAccountNumberGenerator {
|
||||
String officeId = StringUtils.leftPad(account.getOffice().getId().toString(), 2, '0');
|
||||
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
|
||||
|
||||
// Account ID — 5 digits
|
||||
String accountId = StringUtils.leftPad(account.getId().toString(), 5, '0');
|
||||
if (accountId.length() > 5) accountId = accountId.substring(accountId.length() - 5);
|
||||
// Next sequence from existing account numbers
|
||||
String nextSeq = getNextSequence();
|
||||
|
||||
return CBS_PREFIX + glCode + officeId + accountId;
|
||||
return CBS_PREFIX + glCode + officeId + nextSeq;
|
||||
}
|
||||
|
||||
public String preview(Long productId, Long officeId) {
|
||||
@@ -80,13 +104,10 @@ public class CurrentAccountNumberGenerator {
|
||||
String officeStr = StringUtils.leftPad(officeId != null ? officeId.toString() : "0", 2, '0');
|
||||
if (officeStr.length() > 2) officeStr = officeStr.substring(officeStr.length() - 2);
|
||||
|
||||
// Next account ID estimate from m_current_account
|
||||
Long maxId = jdbcTemplate.query(
|
||||
"SELECT MAX(id) FROM m_current_account",
|
||||
rs -> rs.next() ? rs.getLong(1) : 0L);
|
||||
String nextSeq = StringUtils.leftPad(String.valueOf((maxId != null ? maxId : 0L) + 1), 5, '0');
|
||||
if (nextSeq.length() > 5) nextSeq = nextSeq.substring(nextSeq.length() - 5);
|
||||
// Next sequence from existing account numbers
|
||||
String nextSeq = getNextSequence();
|
||||
|
||||
return CBS_PREFIX + glCode + officeStr + nextSeq;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -26,6 +26,8 @@ public interface CurrentAccountReadPlatformService {
|
||||
|
||||
Map<String, Object> retrieveOne(Long accountId);
|
||||
|
||||
Map<String, Object> retrieveOneTransaction(Long accountId, Long transactionId);
|
||||
|
||||
List<Map<String, Object>> retrieveAll();
|
||||
|
||||
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName);
|
||||
|
||||
+2
@@ -40,4 +40,6 @@ public interface CurrentAccountWritePlatformService {
|
||||
CommandProcessingResult modifyApplication(Long accountId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult deleteApplication(Long accountId);
|
||||
|
||||
CommandProcessingResult terminateAccount(Long accountId, JsonCommand command);
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.currentproduct.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class CurrentProductEditPendingApprovalException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public CurrentProductEditPendingApprovalException(final Long productId) {
|
||||
super("error.msg.currentproduct.edit.pending.approval",
|
||||
"Current product with identifier " + productId + " already has an edit pending approval", productId);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.currentproduct.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class CurrentProductNoPendingEditException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public CurrentProductNoPendingEditException(final Long productId) {
|
||||
super("error.msg.currentproduct.no.pending.edit",
|
||||
"Current product with identifier " + productId + " has no edit pending approval", productId);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.currentproduct.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class CurrentProductSelfApprovalNotAllowedException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public CurrentProductSelfApprovalNotAllowedException(final Long productId) {
|
||||
super("error.msg.currentproduct.self.approval.not.allowed",
|
||||
"The maker of the pending edit for current product " + productId + " cannot also approve or reject it", productId);
|
||||
}
|
||||
}
|
||||
+5
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.currentproduct.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsProductTempData;
|
||||
|
||||
public interface CurrentProductReadPlatformService {
|
||||
|
||||
@@ -28,4 +29,8 @@ public interface CurrentProductReadPlatformService {
|
||||
Collection<SavingsProductData> retrieveAllForLookup();
|
||||
|
||||
SavingsProductData retrieveOne(Long productId);
|
||||
|
||||
Collection<SavingsProductTempData> retrieveAllPendingEdits();
|
||||
|
||||
SavingsProductTempData retrievePendingEdit(Long productId);
|
||||
}
|
||||
|
||||
+77
-1
@@ -42,13 +42,23 @@ import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilit
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
||||
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductNoPendingEditException;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsProductTempData;
|
||||
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEdit;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedFieldRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditChargeRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMappingRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimitRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFieldMapper;
|
||||
import java.util.Set;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class CurrentProductReadPlatformServiceImpl implements CurrentProductReadPlatformService {
|
||||
@@ -58,6 +68,11 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
||||
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
||||
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
||||
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
||||
private final ProductPendingEditRepository productPendingEditRepository;
|
||||
private final ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository;
|
||||
private final ProductPendingEditChargeRepository pendingEditChargeRepository;
|
||||
private final ProductPendingEditGlMappingRepository pendingEditGlMappingRepository;
|
||||
private final ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository;
|
||||
|
||||
private final CurrentProductMapper currentProductRowMapper = new CurrentProductMapper();
|
||||
private final CurrentProductLookupMapper currentProductLookupsRowMapper = new CurrentProductLookupMapper();
|
||||
@@ -147,6 +162,64 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
||||
}
|
||||
}
|
||||
|
||||
private static final String PENDING_EDIT_SCHEMA = "spt.id as tempId, spt.product_id as savingsProductId, "
|
||||
+ "cp.name as savingsProductName, spt.createdby_id as makerId, maker.username as makerUsername, "
|
||||
+ "spt.lastmodifiedby_id as checkerId, checker.username as checkerUsername, spt.created_date as createdOn, "
|
||||
+ "spt.lastmodified_date as checkedOn, spt.status as status, spt.reject_reason as rejectReason "
|
||||
+ "from m_saving_product_temp spt "
|
||||
+ "join m_current_product cp on cp.id = spt.product_id "
|
||||
+ "join m_appuser maker on maker.id = spt.createdby_id "
|
||||
+ "left join m_appuser checker on checker.id = spt.lastmodifiedby_id "
|
||||
+ "where spt.product_type = 'CURRENT' ";
|
||||
|
||||
private RowMapper<SavingsProductTempData> getPendingEditRowMapper() {
|
||||
return (rs, rowNum) -> {
|
||||
final SavingsProductTempData data = new SavingsProductTempData();
|
||||
data.setId(rs.getLong("tempId"));
|
||||
data.setSavingsProductId(rs.getLong("savingsProductId"));
|
||||
data.setSavingsProductName(rs.getString("savingsProductName"));
|
||||
data.setMakerId(rs.getLong("makerId"));
|
||||
data.setMakerUsername(rs.getString("makerUsername"));
|
||||
data.setCheckerId(JdbcSupport.getLong(rs, "checkerId"));
|
||||
data.setCheckerUsername(rs.getString("checkerUsername"));
|
||||
data.setCreatedOn(rs.getTimestamp("createdOn") == null ? null : rs.getTimestamp("createdOn").toLocalDateTime());
|
||||
data.setCheckedOn(rs.getTimestamp("checkedOn") == null ? null : rs.getTimestamp("checkedOn").toLocalDateTime());
|
||||
data.setStatus(rs.getString("status"));
|
||||
data.setRejectReason(rs.getString("rejectReason"));
|
||||
|
||||
final Long tempId = data.getId();
|
||||
final java.util.Optional<ProductPendingEdit> opt = this.productPendingEditRepository.findById(tempId);
|
||||
if (opt.isPresent()) {
|
||||
final ProductPendingEdit pendingEdit = opt.get();
|
||||
final Set<String> submittedFields = new java.util.HashSet<>(
|
||||
this.pendingEditSubmittedFieldRepository.findFieldNamesByPendingEditId(pendingEdit.getId()));
|
||||
final Map<String, Object> proposedChanges = ProductPendingEditFieldMapper.reconstructProposedChangesMap(
|
||||
pendingEdit, submittedFields, this.pendingEditChargeRepository, this.pendingEditGlMappingRepository,
|
||||
this.pendingEditFacilityLimitRepository, GSON);
|
||||
data.setProposedChanges(proposedChanges);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SavingsProductTempData> retrieveAllPendingEdits() {
|
||||
this.context.authenticatedUser();
|
||||
final String sql = "select " + PENDING_EDIT_SCHEMA + "and spt.status = 'PENDING' order by spt.created_date desc";
|
||||
return this.jdbcTemplate.query(sql, getPendingEditRowMapper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavingsProductTempData retrievePendingEdit(final Long productId) {
|
||||
this.context.authenticatedUser();
|
||||
final String sql = "select " + PENDING_EDIT_SCHEMA + "and spt.status = 'PENDING' and spt.product_id = ?";
|
||||
try {
|
||||
return this.jdbcTemplate.queryForObject(sql, getPendingEditRowMapper(), new Object[] { productId }); // NOSONAR
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new CurrentProductNoPendingEditException(productId);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CurrentProductMapper implements RowMapper<SavingsProductData> {
|
||||
|
||||
private final String schemaSql;
|
||||
@@ -197,7 +270,9 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
||||
sqlBuilder.append("cp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("cp.balance_swap_option as balanceSwapOption, cp.msme as msme, cp.auto_debit_facility as autoDebitFacility, ");
|
||||
sqlBuilder.append("cp.avg_balance_calc_period as avgBalanceCalcPeriod, cp.valid_for_period as validForPeriod, ");
|
||||
sqlBuilder.append("cp.od_account_closed_zero_balance_in_day_closing as odAccountClosedZeroBalanceInDayClosing ");
|
||||
sqlBuilder.append("cp.od_account_closed_zero_balance_in_day_closing as odAccountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("exists(select 1 from m_saving_product_temp spt where spt.product_id = cp.id "
|
||||
+ "and spt.product_type = 'CURRENT' and spt.status = 'PENDING') as isPendingApproval ");
|
||||
|
||||
sqlBuilder.append("from m_current_product cp ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = cp.currency_code ");
|
||||
@@ -321,6 +396,7 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
||||
productData.setAvgBalanceCalcPeriod(rs.getString("avgBalanceCalcPeriod"));
|
||||
productData.setValidForPeriod(rs.getString("validForPeriod"));
|
||||
productData.setOdAccountClosedZeroBalanceInDayClosing(rs.getBoolean("odAccountClosedZeroBalanceInDayClosing"));
|
||||
productData.setIsPendingApproval(rs.getBoolean("isPendingApproval"));
|
||||
|
||||
return productData;
|
||||
}
|
||||
|
||||
+17
@@ -28,4 +28,21 @@ public interface CurrentProductWritePlatformService {
|
||||
CommandProcessingResult update(Long productId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult delete(Long productId);
|
||||
|
||||
/**
|
||||
* Submits an edit for approval instead of applying it directly. Fails if a pending edit already exists for this
|
||||
* product (that pending row is itself the lock).
|
||||
*/
|
||||
CommandProcessingResult submitEditForApproval(Long productId, JsonCommand command);
|
||||
|
||||
/**
|
||||
* Replays the stored pending edit through the normal update path and marks it approved. Fails if the approving
|
||||
* user is the same user who submitted the edit.
|
||||
*/
|
||||
CommandProcessingResult approveEdit(Long productId);
|
||||
|
||||
/**
|
||||
* Discards the pending edit. Fails if the rejecting user is the same user who submitted the edit.
|
||||
*/
|
||||
CommandProcessingResult rejectEdit(Long productId, String rejectReason);
|
||||
}
|
||||
|
||||
+245
@@ -31,6 +31,9 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
||||
import org.apache.fineract.commands.domain.CommandProcessingResultType;
|
||||
import org.apache.fineract.commands.domain.CommandSource;
|
||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
@@ -38,6 +41,8 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.charge.domain.Charge;
|
||||
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
||||
@@ -50,10 +55,26 @@ import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilit
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
|
||||
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductEditPendingApprovalException;
|
||||
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductNoPendingEditException;
|
||||
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductSelfApprovalNotAllowedException;
|
||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEdit;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditRepository;
|
||||
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedField;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedFieldRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditCharge;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditChargeRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimit;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimitRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMapping;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMappingRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFieldMapper;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -61,6 +82,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@RequiredArgsConstructor
|
||||
public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements CurrentProductWritePlatformService {
|
||||
|
||||
private static final com.google.gson.Gson GSON = new com.google.gson.Gson();
|
||||
private final PlatformSecurityContext context;
|
||||
private final CurrentProductRepository currentProductRepository;
|
||||
private final CurrentProductDataValidator fromApiJsonDataValidator;
|
||||
@@ -69,6 +91,13 @@ public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements Curr
|
||||
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
||||
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
||||
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
||||
private final ProductPendingEditRepository productPendingEditRepository;
|
||||
private final ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository;
|
||||
private final ProductPendingEditChargeRepository pendingEditChargeRepository;
|
||||
private final ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository;
|
||||
private final ProductPendingEditGlMappingRepository pendingEditGlMappingRepository;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final CommandSourceRepository commandSourceRepository;
|
||||
|
||||
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dae) {
|
||||
String msgCode = "error.msg." + DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME;
|
||||
@@ -251,6 +280,222 @@ public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements Curr
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult submitEditForApproval(final Long productId, final JsonCommand command) {
|
||||
|
||||
final AppUser maker = this.context.authenticatedUser();
|
||||
final CurrentProduct product = this.currentProductRepository.findById(productId)
|
||||
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
|
||||
|
||||
if (this.productPendingEditRepository
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_CURRENT, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.isPresent()) {
|
||||
throw new CurrentProductEditPendingApprovalException(productId);
|
||||
}
|
||||
|
||||
this.fromApiJsonDataValidator.validateForUpdate(command.json(), product);
|
||||
|
||||
final ProductPendingEdit pendingEdit = new ProductPendingEdit(ProductPendingEdit.TYPE_CURRENT, productId, maker);
|
||||
|
||||
final java.util.Set<String> submitted = ProductPendingEditFieldMapper.populateCoreFields(command, pendingEdit.getCoreFields());
|
||||
final java.util.Set<String> currentOnlySubmitted = ProductPendingEditFieldMapper.populateCurrentOnlyFields(command, pendingEdit.getCurrentOnlyFields());
|
||||
final boolean facilityConfigSubmitted = ProductPendingEditFieldMapper.populateFacilityConfig(command, pendingEdit.getFacilityConfigFields());
|
||||
final boolean depositLimitKycSubmitted = ProductPendingEditFieldMapper.populateDepositLimitKyc(command, pendingEdit.getDepositLimitKycFields());
|
||||
final boolean glMappingSubmitted = ProductPendingEditFieldMapper.populateGlAccountMapping(command, pendingEdit.getGlAccountMappingFields());
|
||||
|
||||
if (!currentOnlySubmitted.isEmpty()) {
|
||||
submitted.add("avgBalanceCalcPeriod");
|
||||
submitted.add("validForPeriod");
|
||||
submitted.add("odAccountClosedZeroBalanceInDayClosing");
|
||||
}
|
||||
if (facilityConfigSubmitted) {
|
||||
submitted.add("accountFacilityConfig");
|
||||
}
|
||||
if (depositLimitKycSubmitted) {
|
||||
submitted.add("depositLimitKyc");
|
||||
}
|
||||
if (glMappingSubmitted) {
|
||||
submitted.add("glAccountMapping");
|
||||
}
|
||||
|
||||
final CommandSource auditEntry = CommandSource.builder() //
|
||||
.actionName("UPDATE") //
|
||||
.entityName("CURRENTPRODUCT") //
|
||||
.resourceGetUrl("/currentproducts/" + productId) //
|
||||
.resourceId(productId) //
|
||||
.commandAsJson(command.json()) //
|
||||
.maker(maker) //
|
||||
.madeOnDate(DateUtils.getAuditOffsetDateTime()) //
|
||||
.status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()) //
|
||||
.idempotencyKey(java.util.UUID.randomUUID().toString()) //
|
||||
.sanitized(false) //
|
||||
.build();
|
||||
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||
pendingEdit.setCommandSourceId(auditEntry.getId());
|
||||
|
||||
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||
|
||||
for (final String fieldName : submitted) {
|
||||
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, fieldName));
|
||||
}
|
||||
|
||||
final List<Long> chargeIds = ProductPendingEditFieldMapper.extractChargeIds(command);
|
||||
if (chargeIds != null) {
|
||||
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, "charges"));
|
||||
for (final Long chargeId : chargeIds) {
|
||||
this.pendingEditChargeRepository.save(new ProductPendingEditCharge(pendingEdit, chargeId));
|
||||
}
|
||||
}
|
||||
|
||||
final List<ProductPendingEditFacilityLimit> facilityLimits = ProductPendingEditFieldMapper.extractFacilityLimits(command, pendingEdit);
|
||||
if (facilityLimits != null) {
|
||||
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, "accountFacilityLimits"));
|
||||
this.pendingEditFacilityLimitRepository.saveAll(facilityLimits);
|
||||
}
|
||||
|
||||
final List<ProductPendingEditGlMapping> glMappings = ProductPendingEditFieldMapper.extractGlArrayMappings(command, pendingEdit);
|
||||
if (glMappings != null) {
|
||||
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, "glArrayMappings"));
|
||||
this.pendingEditGlMappingRepository.saveAll(glMappings);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withEntityId(product.getId()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult approveEdit(final Long productId) {
|
||||
|
||||
final AppUser checker = this.context.authenticatedUser();
|
||||
final ProductPendingEdit pendingEdit = this.productPendingEditRepository
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_CURRENT, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new CurrentProductNoPendingEditException(productId));
|
||||
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new CurrentProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
final Set<String> submittedFields = new java.util.HashSet<>(
|
||||
this.pendingEditSubmittedFieldRepository.findFieldNamesByPendingEditId(pendingEdit.getId()));
|
||||
final JsonObject root = new JsonObject();
|
||||
root.addProperty("locale", "en");
|
||||
root.addProperty("dateFormat", "yyyy-MM-dd");
|
||||
ProductPendingEditFieldMapper.writeCoreFieldsToJson(root, pendingEdit.getCoreFields(), submittedFields);
|
||||
ProductPendingEditFieldMapper.writeCurrentOnlyFieldsToJson(root, pendingEdit.getCurrentOnlyFields(), submittedFields);
|
||||
ProductPendingEditFieldMapper.writeFacilityConfigToJson(root, pendingEdit.getFacilityConfigFields(),
|
||||
submittedFields.contains("accountFacilityConfig"));
|
||||
ProductPendingEditFieldMapper.writeDepositLimitKycToJson(root, pendingEdit.getDepositLimitKycFields(),
|
||||
submittedFields.contains("depositLimitKyc"));
|
||||
if (submittedFields.contains("glAccountMapping")) {
|
||||
ProductPendingEditFieldMapper.writeGlAccountMappingToJson(root, pendingEdit.getGlAccountMappingFields());
|
||||
}
|
||||
if (submittedFields.contains("charges")) {
|
||||
ProductPendingEditFieldMapper.writeChargesToJson(root,
|
||||
this.pendingEditChargeRepository.findByPendingEditId(pendingEdit.getId()));
|
||||
}
|
||||
if (submittedFields.contains("accountFacilityLimits")) {
|
||||
ProductPendingEditFieldMapper.writeFacilityLimitsToJson(root,
|
||||
this.pendingEditFacilityLimitRepository.findByPendingEditId(pendingEdit.getId()));
|
||||
}
|
||||
if (submittedFields.contains("glArrayMappings")) {
|
||||
ProductPendingEditFieldMapper.writeGlArrayMappingsToJson(root,
|
||||
this.pendingEditGlMappingRepository.findByPendingEditId(pendingEdit.getId()));
|
||||
}
|
||||
final JsonCommand replayedCommand = JsonCommand.fromJsonElement(productId, root, this.fromApiJsonHelper);
|
||||
final CommandProcessingResult result = update(productId, replayedCommand);
|
||||
|
||||
pendingEdit.markApproved(checker);
|
||||
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||
|
||||
final CommandSource auditEntry = findOrCreateAuditEntry(pendingEdit, productId);
|
||||
auditEntry.markAsChecked(checker);
|
||||
auditEntry.updateForAudit(result);
|
||||
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private CommandSource findOrCreateAuditEntry(final ProductPendingEdit pendingEdit, final Long productId) {
|
||||
if (pendingEdit.getCommandSourceId() != null) {
|
||||
final java.util.Optional<CommandSource> existing = this.commandSourceRepository.findById(pendingEdit.getCommandSourceId());
|
||||
if (existing.isPresent()) {
|
||||
return existing.get();
|
||||
}
|
||||
}
|
||||
final Set<String> submittedFields = new java.util.HashSet<>(
|
||||
this.pendingEditSubmittedFieldRepository.findFieldNamesByPendingEditId(pendingEdit.getId()));
|
||||
final JsonObject root = new JsonObject();
|
||||
root.addProperty("locale", "en");
|
||||
root.addProperty("dateFormat", "yyyy-MM-dd");
|
||||
ProductPendingEditFieldMapper.writeCoreFieldsToJson(root, pendingEdit.getCoreFields(), submittedFields);
|
||||
ProductPendingEditFieldMapper.writeCurrentOnlyFieldsToJson(root, pendingEdit.getCurrentOnlyFields(), submittedFields);
|
||||
ProductPendingEditFieldMapper.writeFacilityConfigToJson(root, pendingEdit.getFacilityConfigFields(),
|
||||
submittedFields.contains("accountFacilityConfig"));
|
||||
ProductPendingEditFieldMapper.writeDepositLimitKycToJson(root, pendingEdit.getDepositLimitKycFields(),
|
||||
submittedFields.contains("depositLimitKyc"));
|
||||
if (submittedFields.contains("glAccountMapping")) {
|
||||
ProductPendingEditFieldMapper.writeGlAccountMappingToJson(root, pendingEdit.getGlAccountMappingFields());
|
||||
}
|
||||
if (submittedFields.contains("charges")) {
|
||||
ProductPendingEditFieldMapper.writeChargesToJson(root,
|
||||
this.pendingEditChargeRepository.findByPendingEditId(pendingEdit.getId()));
|
||||
}
|
||||
if (submittedFields.contains("accountFacilityLimits")) {
|
||||
ProductPendingEditFieldMapper.writeFacilityLimitsToJson(root,
|
||||
this.pendingEditFacilityLimitRepository.findByPendingEditId(pendingEdit.getId()));
|
||||
}
|
||||
if (submittedFields.contains("glArrayMappings")) {
|
||||
ProductPendingEditFieldMapper.writeGlArrayMappingsToJson(root,
|
||||
this.pendingEditGlMappingRepository.findByPendingEditId(pendingEdit.getId()));
|
||||
}
|
||||
final String commandJson = GSON.toJson(root);
|
||||
|
||||
final CommandSource auditEntry = CommandSource.builder() //
|
||||
.actionName("UPDATE") //
|
||||
.entityName("CURRENTPRODUCT") //
|
||||
.resourceGetUrl("/currentproducts/" + productId) //
|
||||
.resourceId(productId) //
|
||||
.commandAsJson(commandJson) //
|
||||
.maker(pendingEdit.getMaker()) //
|
||||
.madeOnDate(DateUtils.getAuditOffsetDateTime()) //
|
||||
.status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()) //
|
||||
.idempotencyKey(java.util.UUID.randomUUID().toString()) //
|
||||
.sanitized(false) //
|
||||
.build();
|
||||
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||
pendingEdit.setCommandSourceId(auditEntry.getId());
|
||||
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||
return auditEntry;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult rejectEdit(final Long productId, final String rejectReason) {
|
||||
|
||||
final AppUser checker = this.context.authenticatedUser();
|
||||
final ProductPendingEdit pendingEdit = this.productPendingEditRepository
|
||||
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_CURRENT, productId, ProductPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new CurrentProductNoPendingEditException(productId));
|
||||
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new CurrentProductSelfApprovalNotAllowedException(productId);
|
||||
}
|
||||
|
||||
pendingEdit.markRejected(checker, rejectReason);
|
||||
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||
|
||||
final CommandSource auditEntry = findOrCreateAuditEntry(pendingEdit, productId);
|
||||
auditEntry.markAsRejected(checker);
|
||||
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withEntityId(productId) //
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult delete(final Long productId) {
|
||||
|
||||
+28
-4
@@ -19,7 +19,14 @@
|
||||
package org.apache.fineract.portfolio.currentproduct.starter;
|
||||
|
||||
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedFieldRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditChargeRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimitRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMappingRepository;
|
||||
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductAssembler;
|
||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
|
||||
@@ -43,9 +50,17 @@ public class CurrentProductConfiguration {
|
||||
public CurrentProductReadPlatformService currentProductReadPlatformService(PlatformSecurityContext context,
|
||||
JdbcTemplate jdbcTemplate, CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
||||
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
||||
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
|
||||
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository,
|
||||
ProductPendingEditRepository productPendingEditRepository,
|
||||
ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository,
|
||||
ProductPendingEditChargeRepository pendingEditChargeRepository,
|
||||
ProductPendingEditGlMappingRepository pendingEditGlMappingRepository,
|
||||
ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository) {
|
||||
return new CurrentProductReadPlatformServiceImpl(context, jdbcTemplate, currentFacilityConfigRepository,
|
||||
currentFacilityLimitRepository, currentDepositLimitKycRepository);
|
||||
currentFacilityLimitRepository, currentDepositLimitKycRepository,
|
||||
productPendingEditRepository, pendingEditSubmittedFieldRepository,
|
||||
pendingEditChargeRepository, pendingEditGlMappingRepository,
|
||||
pendingEditFacilityLimitRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -56,9 +71,18 @@ public class CurrentProductConfiguration {
|
||||
ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
|
||||
CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
||||
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
||||
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
|
||||
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository,
|
||||
ProductPendingEditRepository productPendingEditRepository,
|
||||
ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository,
|
||||
ProductPendingEditChargeRepository pendingEditChargeRepository,
|
||||
ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository,
|
||||
ProductPendingEditGlMappingRepository pendingEditGlMappingRepository,
|
||||
FromJsonHelper fromApiJsonHelper,
|
||||
CommandSourceRepository commandSourceRepository) {
|
||||
return new CurrentProductWritePlatformServiceJpaRepositoryImpl(context, currentProductRepository, currentProductDataValidator,
|
||||
currentProductAssembler, accountMappingWritePlatformService, currentFacilityConfigRepository,
|
||||
currentFacilityLimitRepository, currentDepositLimitKycRepository);
|
||||
currentFacilityLimitRepository, currentDepositLimitKycRepository, productPendingEditRepository,
|
||||
pendingEditSubmittedFieldRepository, pendingEditChargeRepository, pendingEditFacilityLimitRepository,
|
||||
pendingEditGlMappingRepository, fromApiJsonHelper, commandSourceRepository);
|
||||
}
|
||||
}
|
||||
|
||||
+719
-719
File diff suppressed because it is too large
Load Diff
@@ -99,6 +99,10 @@ dependencies {
|
||||
// Bundles ZXing transitively for QR PNG output (used by ZxingPngQrGenerator).
|
||||
'dev.samstevens.totp:totp:1.7.1',
|
||||
|
||||
// Nimbus JOSE+JWT — RS256 sign/verify for the IMPS_BANK_API_SPEC_CBS_1.0 envelope
|
||||
// (see org.apache.fineract.infrastructure.imps.crypto.ImpsJwtUtil).
|
||||
'com.nimbusds:nimbus-jose-jwt:9.37',
|
||||
|
||||
'org.apache.commons:commons-lang3',
|
||||
'commons-io:commons-io',
|
||||
'org.apache.poi:poi',
|
||||
|
||||
@@ -0,0 +1,975 @@
|
||||
> Task :buildSrc:extractPluginRequests UP-TO-DATE
|
||||
> Task :buildSrc:generatePluginAdapters UP-TO-DATE
|
||||
> Task :buildSrc:compileJava UP-TO-DATE
|
||||
> Task :buildSrc:compileGroovy UP-TO-DATE
|
||||
> Task :buildSrc:compileGroovyPlugins UP-TO-DATE
|
||||
> Task :buildSrc:pluginDescriptors UP-TO-DATE
|
||||
> Task :buildSrc:processResources UP-TO-DATE
|
||||
> Task :buildSrc:classes UP-TO-DATE
|
||||
> Task :buildSrc:jar UP-TO-DATE
|
||||
|
||||
> Configure project :
|
||||
matching ref: COMMIT - 7dea88d30f72365945bed2160d2ba72dccdc80b7
|
||||
ref configuration: COMMIT - pattern: null
|
||||
version: ${describe.tag.version.major}.${describe.tag.version.minor.next}.0-SNAPSHOT
|
||||
describeTagPattern: .*(\d+\.\d+\.\d+).*
|
||||
describeTagFirstParent: false
|
||||
|
||||
project version: 1.15.0-SNAPSHOT
|
||||
|
||||
> Task :spotlessInternalRegisterDependencies UP-TO-DATE
|
||||
> Task :fineract-current-product:processResources NO-SOURCE
|
||||
> Task :fineract-mix:processResources NO-SOURCE
|
||||
> Task :fineract-rates:processResources UP-TO-DATE
|
||||
> Task :fineract-core:processResources UP-TO-DATE
|
||||
> Task :fineract-cob:processResources UP-TO-DATE
|
||||
> Task :fineract-branch:processResources UP-TO-DATE
|
||||
> Task :fineract-security:processResources NO-SOURCE
|
||||
> Task :fineract-loan-origination:processResources UP-TO-DATE
|
||||
> Task :fineract-savings:processResources UP-TO-DATE
|
||||
> Task :fineract-accounting:processResources UP-TO-DATE
|
||||
> Task :fineract-report:processResources UP-TO-DATE
|
||||
> Task :fineract-charge:processResources UP-TO-DATE
|
||||
> Task :fineract-investor:processResources UP-TO-DATE
|
||||
> Task :fineract-loan:processResources UP-TO-DATE
|
||||
> Task :fineract-client:processResources UP-TO-DATE
|
||||
> Task :fineract-progressive-loan:processResources UP-TO-DATE
|
||||
> Task :fineract-working-capital-loan:processResources UP-TO-DATE
|
||||
> Task :fineract-document:processResources NO-SOURCE
|
||||
> Task :fineract-tax:processResources UP-TO-DATE
|
||||
> Task :fineract-avro-schemas:preprocessAvroSchemas UP-TO-DATE
|
||||
> Task :fineract-validation:compileJava UP-TO-DATE
|
||||
> Task :fineract-command:compileJava UP-TO-DATE
|
||||
> Task :fineract-validation:processResources UP-TO-DATE
|
||||
> Task :fineract-validation:classes UP-TO-DATE
|
||||
> Task :fineract-command:processResources UP-TO-DATE
|
||||
> Task :fineract-command:classes UP-TO-DATE
|
||||
> Task :fineract-command:jar UP-TO-DATE
|
||||
> Task :fineract-validation:jar UP-TO-DATE
|
||||
> Task :fineract-avro-schemas:spotlessJson UP-TO-DATE
|
||||
> Task :fineract-avro-schemas:spotlessJsonApply UP-TO-DATE
|
||||
> Task :fineract-avro-schemas:buildJavaSdk UP-TO-DATE
|
||||
> Task :fineract-avro-schemas:compileJava UP-TO-DATE
|
||||
> Task :fineract-avro-schemas:processResources UP-TO-DATE
|
||||
> Task :fineract-avro-schemas:classes UP-TO-DATE
|
||||
> Task :fineract-avro-schemas:jar UP-TO-DATE
|
||||
> Task :fineract-working-capital-loan:compileJava UP-TO-DATE
|
||||
> Task :fineract-working-capital-loan:classes UP-TO-DATE
|
||||
> Task :fineract-working-capital-loan:jar UP-TO-DATE
|
||||
> Task :fineract-core:compileJava UP-TO-DATE
|
||||
> Task :fineract-core:classes UP-TO-DATE
|
||||
> Task :fineract-core:jar UP-TO-DATE
|
||||
> Task :fineract-mix:compileJava UP-TO-DATE
|
||||
> Task :fineract-tax:compileJava UP-TO-DATE
|
||||
> Task :fineract-mix:classes UP-TO-DATE
|
||||
> Task :fineract-report:compileJava UP-TO-DATE
|
||||
> Task :fineract-tax:classes UP-TO-DATE
|
||||
> Task :fineract-mix:jar UP-TO-DATE
|
||||
> Task :fineract-rates:compileJava UP-TO-DATE
|
||||
> Task :fineract-report:classes UP-TO-DATE
|
||||
> Task :fineract-cob:compileJava UP-TO-DATE
|
||||
> Task :fineract-rates:classes UP-TO-DATE
|
||||
> Task :fineract-cob:classes UP-TO-DATE
|
||||
> Task :fineract-tax:jar UP-TO-DATE
|
||||
> Task :fineract-report:jar UP-TO-DATE
|
||||
> Task :fineract-cob:jar UP-TO-DATE
|
||||
> Task :fineract-rates:jar UP-TO-DATE
|
||||
> Task :fineract-document:compileJava UP-TO-DATE
|
||||
> Task :fineract-document:classes UP-TO-DATE
|
||||
> Task :fineract-security:compileJava UP-TO-DATE
|
||||
> Task :fineract-document:jar UP-TO-DATE
|
||||
> Task :fineract-security:classes UP-TO-DATE
|
||||
> Task :fineract-security:jar UP-TO-DATE
|
||||
> Task :fineract-charge:compileJava UP-TO-DATE
|
||||
> Task :fineract-charge:classes UP-TO-DATE
|
||||
> Task :fineract-charge:jar UP-TO-DATE
|
||||
> Task :fineract-accounting:compileJava UP-TO-DATE
|
||||
> Task :fineract-accounting:classes UP-TO-DATE
|
||||
> Task :fineract-accounting:jar UP-TO-DATE
|
||||
> Task :fineract-branch:compileJava UP-TO-DATE
|
||||
> Task :fineract-branch:classes UP-TO-DATE
|
||||
> Task :fineract-loan:compileJava UP-TO-DATE
|
||||
> Task :fineract-loan:classes UP-TO-DATE
|
||||
> Task :fineract-branch:jar UP-TO-DATE
|
||||
> Task :fineract-loan:jar UP-TO-DATE
|
||||
> Task :fineract-savings:compileJava UP-TO-DATE
|
||||
> Task :fineract-savings:classes UP-TO-DATE
|
||||
> Task :fineract-savings:jar UP-TO-DATE
|
||||
> Task :fineract-loan-origination:compileJava UP-TO-DATE
|
||||
> Task :fineract-investor:compileJava UP-TO-DATE
|
||||
> Task :fineract-progressive-loan:compileJava UP-TO-DATE
|
||||
> Task :fineract-loan-origination:classes UP-TO-DATE
|
||||
> Task :fineract-progressive-loan:classes UP-TO-DATE
|
||||
> Task :fineract-investor:classes UP-TO-DATE
|
||||
> Task :fineract-progressive-loan:jar UP-TO-DATE
|
||||
> Task :fineract-loan-origination:jar UP-TO-DATE
|
||||
> Task :fineract-investor:jar UP-TO-DATE
|
||||
> Task :fineract-current-product:compileJava UP-TO-DATE
|
||||
> Task :fineract-current-product:classes UP-TO-DATE
|
||||
> Task :fineract-current-product:jar UP-TO-DATE
|
||||
> Task :fineract-provider:generateGitProperties
|
||||
> Task :fineract-provider:compileJava UP-TO-DATE
|
||||
> Task :fineract-provider:generateRebelMain UP-TO-DATE
|
||||
> Task :fineract-provider:processResources
|
||||
> Task :fineract-provider:classes
|
||||
> Task :fineract-provider:compileTestJava UP-TO-DATE
|
||||
> Task :fineract-provider:prepareInputYaml UP-TO-DATE
|
||||
> Task :fineract-provider:processTestResources UP-TO-DATE
|
||||
> Task :fineract-provider:testClasses UP-TO-DATE
|
||||
> Task :fineract-provider:resolve
|
||||
> Task :fineract-provider:resolveMainClassName
|
||||
|
||||
> Task :fineract-provider:bootRun
|
||||
19:17:05,132 |-WARN in ch.qos.logback.core.joran.action.ConversionRuleAction - [converterClass] attribute is deprecated and replaced by [class]. See element [conversionRule] near line 30
|
||||
|
||||
_ _ _____ _ _
|
||||
/ \ _ __ __ _ ___| |__ ___ | ___(_)_ __ ___ _ __ __ _ ___| |_
|
||||
/ _ \ | '_ \ / _` |/ __| '_ \ / _ \ | |_ | | '_ \ / _ \ '__/ _` |/ __| __|
|
||||
/ ___ \| |_) | (_| | (__| | | | __/ | _| | | | | | __/ | | (_| | (__| |_
|
||||
/_/ \_\ .__/ \__,_|\___|_| |_|\___| |_| |_|_| |_|\___|_| \__,_|\___|\__|
|
||||
|_|
|
||||
|
||||
[2m[37m(c) 2015-2024 Apache Fineract (https://fineract.apache.org)[30m
|
||||
|
||||
[2m[37mPowered by Spring Boot 3.5.6[30m
|
||||
|
||||
|
||||
[2m2026-05-29 19:17:05.618[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36morg.apache.fineract.ServerApplication [0;39m [2m:[0;39m Starting ServerApplication using Java 21.0.9 with PID 7760 (C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main started by adity in C:\Users\adity\Music\CBS\fineract\fineract-provider)
|
||||
[2m2026-05-29 19:17:05.622[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36morg.apache.fineract.ServerApplication [0;39m [2m:[0;39m No active profile set, falling back to 1 default profile: "default"
|
||||
[2m2026-05-29 19:17:10.836[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
[2m2026-05-29 19:17:10.836[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Bootstrapping Spring Data JPA repositories in DEFAULT mode.
|
||||
[2m2026-05-29 19:17:11.794[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.documentmanagement.domain.DocumentRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
|
||||
[2m2026-05-29 19:17:11.797[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.documentmanagement.domain.ImageRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
|
||||
[2m2026-05-29 19:17:11.811[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.mix.domain.MixReportXBRLNamespaceRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
|
||||
[2m2026-05-29 19:17:11.813[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.mix.domain.MixTaxonomyMappingRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
|
||||
[2m2026-05-29 19:17:11.815[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.mix.domain.MixTaxonomyRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
|
||||
[2m2026-05-29 19:17:11.887[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.command.persistence.domain.CommandRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
|
||||
[2m2026-05-29 19:17:13.120[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Finished Spring Data repository scanning in 2267 ms. Found 224 JPA repository interfaces.
|
||||
[2m2026-05-29 19:17:13.130[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.f.s.DefaultListableBeanFactory [0;39m [2m:[0;39m Overriding bean definition for bean 'jpaAuditingHandler' with a different definition: replacing [Root bean: class=org.springframework.data.auditing.AuditingHandler; scope=; abstract=false; lazyInit=null; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=null; factoryMethodName=from; initMethodNames=null; destroyMethodNames=null] with [Root bean: class=org.apache.fineract.infrastructure.core.auditing.CustomAuditingHandler; scope=prototype; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null]
|
||||
[2m2026-05-29 19:17:13.204[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.f.s.DefaultListableBeanFactory [0;39m [2m:[0;39m Overriding bean definition for bean 'transactionManager' with a different definition: replacing [Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=JPAConfig; factoryMethodName=transactionManager; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [org/apache/fineract/infrastructure/core/config/jpa/JPAConfig.class]] with [Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=scheduledJobRunnerConfig; factoryMethodName=transactionManager; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [org/apache/fineract/infrastructure/jobs/ScheduledJobRunnerConfig.class]]
|
||||
[2m2026-05-29 19:17:13.206[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.c.c.annotation.BatchRegistrar [0;39m [2m:[0;39m Bean jobRepository already defined in the application context, skipping the registration of a jobRepository
|
||||
[2m2026-05-29 19:17:13.206[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.c.c.annotation.BatchRegistrar [0;39m [2m:[0;39m Bean jobExplorer already defined in the application context, skipping the registration of a jobExplorer
|
||||
[2m2026-05-29 19:17:13.206[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.c.c.annotation.BatchRegistrar [0;39m [2m:[0;39m Bean jobLauncher already defined in the application context, skipping the registration of a jobLauncher
|
||||
[2m2026-05-29 19:17:13.208[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.c.c.annotation.BatchRegistrar [0;39m [2m:[0;39m Finished Spring Batch infrastructure beans configuration in 3 ms.
|
||||
[2m2026-05-29 19:17:14.107[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Multiple Spring Data modules found, entering strict repository configuration mode
|
||||
[2m2026-05-29 19:17:14.108[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
|
||||
[2m2026-05-29 19:17:14.408[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.provisioning.domain.ProvisioningEntryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.409[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.adhocquery.domain.AdHocRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.409[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.cob.domain.LoanAccountLockRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.bulkimport.domain.ImportDocumentRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.configuration.domain.ExternalServicesPropertiesRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.412[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.CreditBureauConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.412[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.CreditBureauLoanProductMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.412[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.CreditBureauRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.413[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.CreditReportRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.413[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.OrganisationCreditBureauRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.413[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.TokenRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.EntityDatatableChecksRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.RegisteredDatatableRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.ReportParameterRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.ReportParameterUsageRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.ReportRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityRelationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityToEntityMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.hooks.domain.HookConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.hooks.domain.HookRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.416[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.hooks.domain.HookTemplateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.416[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.imps.domain.ImpsTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.416[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.416[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.domain.ScheduledJobRunHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.domain.SchedulerDetailRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.service.aggregationjob.domain.JournalEntryAggregationTrackingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.service.aggregationjob.domain.JournalEntrySummaryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.419[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.419[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRunHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.420[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.security.otp.domain.OtpLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.421[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.421[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.survey.domain.LikelihoodRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.421[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.interoperation.domain.InteropIdentifierRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.421[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.notification.domain.NotificationMapperRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.422[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.notification.domain.NotificationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.422[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.holiday.domain.HolidayRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.422[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.office.domain.OfficeTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.423[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.provisioning.domain.ProvisioningCategoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.423[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteriaDefinitionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.423[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteriaRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.424[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.staff.domain.StaffRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.424[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.424[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.425[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.425[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.account.domain.AccountTransferRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.426[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.account.domain.StandingInstructionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.426[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.address.domain.AddressRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.426[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.address.domain.FieldConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.calendar.domain.CalendarHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.calendar.domain.CalendarRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.ckyc.domain.CkycBatchRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientAddressRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientChargeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.428[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientFamilyMembersRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.428[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientNonPersonRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.428[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.428[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientTransferDetailsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.429[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagementRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.430[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementDomainRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.430[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.collateral.domain.LoanCollateralRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.431[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.BankIfscRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.431[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.CustomerTransferLimitRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.431[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.FundTransferEventRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.FundTransferRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.TransferLimitConfigRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.VerificationTokenRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fund.domain.FundRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.group.domain.GroupLevelRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.group.domain.GroupRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.group.domain.GroupRoleRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanproduct.productmix.domain.ProductMixRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.meeting.attendance.domain.ClientAttendanceRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.meeting.domain.MeetingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.note.domain.NoteRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.rate.domain.RateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffectiveRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.435[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.438[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FixedDepositAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.438[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FixedDepositProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.438[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateEffectiveRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateSlabHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RdInterestRateEffectiveRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RdInterestRateSlabHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RecurringDepositAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.account.domain.SelfBeneficiariesTPTRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.device.domain.DeviceRegistrationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.pockets.domain.PocketRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistrationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountDividendRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.shareproducts.domain.ShareProductDividentPayOutDetailsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.441[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.shareproducts.domain.ShareProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.441[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.spm.domain.LookupTableRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.442[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.spm.domain.ScorecardRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.444[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.spm.domain.SurveyRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.444[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.template.domain.TemplateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.445[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.AppUserPreviousPasswordRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.445[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.AppUserRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.PasswordValidationPolicyRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.PermissionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.RoleRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributesRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.447[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.447[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.447[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.447[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.448[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.448[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.448[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.449[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.449[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.449[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanAccrualActivityRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanApprovedAmountHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidByRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagementRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetailsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanStatusChangeHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.454[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.454[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.454[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.454[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlabRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FacilityChargeMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.GSIMRepositoy; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RecurringDepositProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsAccountChargeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsAccountFacilityConfigRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsProductDepositLimitKycRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfigRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.cob.domain.BatchBusinessStepRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.teller.domain.CashierRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.teller.domain.CashierTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.teller.domain.TellerRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.teller.domain.TellerTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.closure.domain.GLClosureRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.glaccount.domain.TrialBalanceRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.459[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.459[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.rule.domain.AccountingRuleRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.459[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.459[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.charge.domain.ChargeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.462[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.tax.domain.TaxComponentRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.462[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.tax.domain.TaxGroupRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.462[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.security.domain.TFAccessTokenRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.462[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.security.domain.TwoFactorConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.464[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.glaccount.domain.GLAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.commands.domain.CommandSourceRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.cache.domain.PlatformCacheRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.codes.domain.CodeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.codes.domain.CodeValueRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.rbifeed.domain.RbiFeedItemRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.office.domain.OfficeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.cfcfrms.domain.CfcfrmsSimulatorLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.cfcfrms.domain.ComplaintAuditLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.468[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.cfcfrms.domain.ComplaintTicketRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.468[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientForm60Repository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.470[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientIdentifierRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.470[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientKycRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientNomineeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientEouRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientImportExportRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientMsmeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientMudraBorrowerRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientNonPrioritySectorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientPrioritySectorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientStartupIndiaRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.mobilelookup.domain.MobileLookupLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.sanctions.domain.SanctionsEntryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.473[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.sanctions.domain.SanctionsScreeningLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.473[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.dsc.domain.BankUserDscRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.473[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.dsc.domain.DscChallengeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.473[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.mfa.domain.UserMfaFactorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
|
||||
[2m2026-05-29 19:17:14.768[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Finished Spring Data repository scanning in 657 ms. Found 6 JDBC repository interfaces.
|
||||
[2m2026-05-29 19:17:16.886[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mfaultConfiguringBeanFactoryPostProcessor[0;39m [2m:[0;39m No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
|
||||
[2m2026-05-29 19:17:16.895[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mfaultConfiguringBeanFactoryPostProcessor[0;39m [2m:[0;39m No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
|
||||
[2m2026-05-29 19:17:18.104[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m Bean 'fineract-org.apache.fineract.infrastructure.core.config.FineractProperties' of type [org.apache.fineract.infrastructure.core.config.FineractProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
|
||||
[2m2026-05-29 19:17:18.117[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m Bean 'cacheConfig' of type [org.apache.fineract.infrastructure.core.config.cache.CacheConfig$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
|
||||
[2m2026-05-29 19:17:18.335[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36morg.reflections.Reflections [0;39m [2m:[0;39m Reflections took 7 ms to scan 1 urls, producing 0 keys and 0 values
|
||||
[2m2026-05-29 19:17:18.339[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.a.f.i.core.config.cache.CacheConfig [0;39m [2m:[0;39m The following cache configurations are defined but cache does not exists: [userTFAccessToken]
|
||||
[2m2026-05-29 19:17:18.343[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m Bean 'ehCacheManager' of type [org.springframework.cache.jcache.JCacheCacheManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
|
||||
[2m2026-05-29 19:17:18.354[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m Bean 'defaultCacheManager' of type [org.apache.fineract.infrastructure.core.config.cache.TransactionBoundCacheManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
|
||||
[2m2026-05-29 19:17:18.360[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m Bean 'runtimeDelegatingCacheManager' of type [org.apache.fineract.infrastructure.cache.service.RuntimeDelegatingCacheManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
|
||||
[2m2026-05-29 19:17:18.364[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m Bean 'platformCacheConfiguration' of type [org.apache.fineract.infrastructure.cache.PlatformCacheConfiguration$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
|
||||
[2m2026-05-29 19:17:18.407[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m Bean 'samplingConfiguration' of type [org.apache.fineract.infrastructure.core.diagnostics.performance.sampling.core.SamplingConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
|
||||
[2m2026-05-29 19:17:18.415[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m Bean 'samplingServiceFactory' of type [org.apache.fineract.infrastructure.core.diagnostics.performance.sampling.core.SamplingServiceFactory] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
|
||||
[2m2026-05-29 19:17:19.255[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.w.embedded.tomcat.TomcatWebServer [0;39m [2m:[0;39m Tomcat initialized with port 8443 (https)
|
||||
[2m2026-05-29 19:17:19.278[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.apache.catalina.core.StandardService [0;39m [2m:[0;39m Starting service [Tomcat]
|
||||
[2m2026-05-29 19:17:19.279[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.apache.catalina.core.StandardEngine [0;39m [2m:[0;39m Starting Servlet engine: [Apache Tomcat/10.1.49]
|
||||
[2m2026-05-29 19:17:19.328[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.a.c.c.C.[.[.[/fineract-provider] [0;39m [2m:[0;39m Initializing Spring embedded WebApplicationContext
|
||||
[2m2026-05-29 19:17:19.329[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mw.s.c.ServletWebServerApplicationContext[0;39m [2m:[0;39m Root WebApplicationContext: initialization completed in 13673 ms
|
||||
[2m2026-05-29 19:17:19.577[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Starting...
|
||||
[2m2026-05-29 19:17:19.860[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.pool.HikariPool [0;39m [2m:[0;39m HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@5917b984
|
||||
[2m2026-05-29 19:17:19.861[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Start completed.
|
||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m Upgrading tenant store DB at localhost:5432
|
||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.username: postgres
|
||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.password: ****
|
||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.parameters:
|
||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.timezone: Asia/Kolkata
|
||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.description: Default Demo Tenant
|
||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.identifier: default
|
||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.name: fineract_default
|
||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - liquibase.analytics.enabled: false
|
||||
[2m2026-05-29 19:17:25.487[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m Tenant store upgrade finished
|
||||
[2m2026-05-29 19:17:25.487[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m Upgrading all tenants
|
||||
[2m2026-05-29 19:17:25.501[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m Upgrade for tenant default has started
|
||||
[2m2026-05-29 19:17:25.842[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-2 - Starting...
|
||||
[2m2026-05-29 19:17:25.946[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.pool.HikariPool [0;39m [2m:[0;39m HikariPool-2 - Added connection org.postgresql.jdbc.PgConnection@625805c1
|
||||
[2m2026-05-29 19:17:25.946[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-2 - Start completed.
|
||||
[2m2026-05-29 19:17:31.700[0;39m [31m-ERROR[0;39m [35m7760[0;39m [31m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mliquibase.changelog [0;39m [2m:[0;39m ChangeSet db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract encountered an exception.
|
||||
|
||||
liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
|
||||
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
|
||||
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:520)
|
||||
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:85)
|
||||
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:188)
|
||||
at liquibase.executor.AbstractExecutor.execute(AbstractExecutor.java:148)
|
||||
at liquibase.database.AbstractJdbcDatabase.executeStatements(AbstractJdbcDatabase.java:1198)
|
||||
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:816)
|
||||
at liquibase.changelog.visitor.UpdateVisitor.executeAcceptedChange(UpdateVisitor.java:127)
|
||||
at liquibase.changelog.visitor.UpdateVisitor.visit(UpdateVisitor.java:71)
|
||||
at liquibase.changelog.ChangeLogIterator.lambda$run$0(ChangeLogIterator.java:133)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.changelog.ChangeLogIterator.lambda$run$1(ChangeLogIterator.java:122)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.Scope.child(Scope.java:282)
|
||||
at liquibase.Scope.child(Scope.java:286)
|
||||
at liquibase.changelog.ChangeLogIterator.run(ChangeLogIterator.java:91)
|
||||
at liquibase.command.core.AbstractUpdateCommandStep.lambda$run$0(AbstractUpdateCommandStep.java:114)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.command.core.AbstractUpdateCommandStep.run(AbstractUpdateCommandStep.java:112)
|
||||
at liquibase.command.core.UpdateCommandStep.run(UpdateCommandStep.java:100)
|
||||
at liquibase.command.CommandScope.lambda$execute$6(CommandScope.java:263)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:201)
|
||||
at liquibase.command.CommandScope.execute(CommandScope.java:251)
|
||||
at liquibase.Liquibase.lambda$update$0(Liquibase.java:216)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.Liquibase.runInScope(Liquibase.java:1366)
|
||||
at liquibase.Liquibase.update(Liquibase.java:205)
|
||||
at liquibase.Liquibase.update(Liquibase.java:188)
|
||||
at liquibase.integration.spring.SpringLiquibase.performUpdate(SpringLiquibase.java:327)
|
||||
at liquibase.integration.spring.SpringLiquibase.lambda$afterPropertiesSet$0(SpringLiquibase.java:279)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:272)
|
||||
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenant(TenantDatabaseUpgradeService.java:205)
|
||||
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.lambda$upgradeIndividualTenants$1(TenantDatabaseUpgradeService.java:146)
|
||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
|
||||
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null).
|
||||
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2736)
|
||||
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2421)
|
||||
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:372)
|
||||
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:525)
|
||||
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:435)
|
||||
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:357)
|
||||
at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:342)
|
||||
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:318)
|
||||
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:313)
|
||||
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:95)
|
||||
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java)
|
||||
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:512)
|
||||
... 52 common frames omitted
|
||||
|
||||
[2m2026-05-29 19:17:31.748[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-2 - Shutdown initiated...
|
||||
[2m2026-05-29 19:17:31.750[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-2 - Shutdown completed.
|
||||
[2m2026-05-29 19:17:31.752[0;39m [31m-ERROR[0;39m [35m7760[0;39m [31m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m Exception:
|
||||
|
||||
java.util.concurrent.ExecutionException: java.lang.RuntimeException: Exception while upgrading tenant default
|
||||
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
|
||||
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
|
||||
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenants(TenantDatabaseUpgradeService.java:155)
|
||||
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.afterPropertiesSet(TenantDatabaseUpgradeService.java:93)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:691)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:513)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1725)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1474)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:606)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1698)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansWithAnnotation(DefaultListableBeanFactory.java:796)
|
||||
at org.springframework.context.support.AbstractApplicationContext.getBeansWithAnnotation(AbstractApplicationContext.java:1440)
|
||||
at org.apache.fineract.infrastructure.core.jersey.JerseyConfig.setup(JerseyConfig.java:58)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMethod.invoke(InitDestroyAnnotationBeanPostProcessor.java:457)
|
||||
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:401)
|
||||
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:219)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:429)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1818)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:413)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
|
||||
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:230)
|
||||
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:221)
|
||||
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:106)
|
||||
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:95)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:271)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:245)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:52)
|
||||
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4452)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1201)
|
||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1191)
|
||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
|
||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:81)
|
||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:747)
|
||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:770)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1201)
|
||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1191)
|
||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
|
||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:81)
|
||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:747)
|
||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:201)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:410)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:868)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.startup.Tomcat.start(Tomcat.java:436)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:128)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:107)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:517)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:219)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:193)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:167)
|
||||
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
|
||||
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)
|
||||
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)
|
||||
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)
|
||||
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:149)
|
||||
at org.apache.fineract.ServerApplication.main(ServerApplication.java:57)
|
||||
Caused by: java.lang.RuntimeException: Exception while upgrading tenant default
|
||||
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenant(TenantDatabaseUpgradeService.java:208)
|
||||
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.lambda$upgradeIndividualTenants$1(TenantDatabaseUpgradeService.java:146)
|
||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||
Caused by: liquibase.exception.LiquibaseException: liquibase.exception.CommandExecutionException: liquibase.exception.LiquibaseException: liquibase.exception.MigrationFailedException: Migration failed for changeset db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract:
|
||||
Reason: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
|
||||
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
|
||||
at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:289)
|
||||
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenant(TenantDatabaseUpgradeService.java:205)
|
||||
... 5 common frames omitted
|
||||
Caused by: liquibase.exception.CommandExecutionException: liquibase.exception.LiquibaseException: liquibase.exception.MigrationFailedException: Migration failed for changeset db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract:
|
||||
Reason: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
|
||||
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
|
||||
at liquibase.command.CommandScope.lambda$execute$6(CommandScope.java:310)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:201)
|
||||
at liquibase.command.CommandScope.execute(CommandScope.java:251)
|
||||
at liquibase.Liquibase.lambda$update$0(Liquibase.java:216)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.Liquibase.runInScope(Liquibase.java:1366)
|
||||
at liquibase.Liquibase.update(Liquibase.java:205)
|
||||
at liquibase.Liquibase.update(Liquibase.java:188)
|
||||
at liquibase.integration.spring.SpringLiquibase.performUpdate(SpringLiquibase.java:327)
|
||||
at liquibase.integration.spring.SpringLiquibase.lambda$afterPropertiesSet$0(SpringLiquibase.java:279)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:272)
|
||||
... 6 common frames omitted
|
||||
Caused by: liquibase.exception.LiquibaseException: liquibase.exception.MigrationFailedException: Migration failed for changeset db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract:
|
||||
Reason: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
|
||||
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
|
||||
at liquibase.changelog.ChangeLogIterator.run(ChangeLogIterator.java:155)
|
||||
at liquibase.command.core.AbstractUpdateCommandStep.lambda$run$0(AbstractUpdateCommandStep.java:114)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.command.core.AbstractUpdateCommandStep.run(AbstractUpdateCommandStep.java:112)
|
||||
at liquibase.command.core.UpdateCommandStep.run(UpdateCommandStep.java:100)
|
||||
at liquibase.command.CommandScope.lambda$execute$6(CommandScope.java:263)
|
||||
... 24 common frames omitted
|
||||
Caused by: liquibase.exception.MigrationFailedException: Migration failed for changeset db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract:
|
||||
Reason: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
|
||||
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
|
||||
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:873)
|
||||
at liquibase.changelog.visitor.UpdateVisitor.executeAcceptedChange(UpdateVisitor.java:127)
|
||||
at liquibase.changelog.visitor.UpdateVisitor.visit(UpdateVisitor.java:71)
|
||||
at liquibase.changelog.ChangeLogIterator.lambda$run$0(ChangeLogIterator.java:133)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.changelog.ChangeLogIterator.lambda$run$1(ChangeLogIterator.java:122)
|
||||
at liquibase.Scope.lambda$child$0(Scope.java:216)
|
||||
at liquibase.Scope.child(Scope.java:225)
|
||||
at liquibase.Scope.child(Scope.java:215)
|
||||
at liquibase.Scope.child(Scope.java:194)
|
||||
at liquibase.Scope.child(Scope.java:282)
|
||||
at liquibase.Scope.child(Scope.java:286)
|
||||
at liquibase.changelog.ChangeLogIterator.run(ChangeLogIterator.java:91)
|
||||
... 32 common frames omitted
|
||||
Caused by: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
|
||||
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
|
||||
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:520)
|
||||
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:85)
|
||||
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:188)
|
||||
at liquibase.executor.AbstractExecutor.execute(AbstractExecutor.java:148)
|
||||
at liquibase.database.AbstractJdbcDatabase.executeStatements(AbstractJdbcDatabase.java:1198)
|
||||
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:816)
|
||||
... 47 common frames omitted
|
||||
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
|
||||
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null).
|
||||
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2736)
|
||||
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2421)
|
||||
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:372)
|
||||
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:525)
|
||||
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:435)
|
||||
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:357)
|
||||
at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:342)
|
||||
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:318)
|
||||
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:313)
|
||||
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:95)
|
||||
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java)
|
||||
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:512)
|
||||
... 52 common frames omitted
|
||||
|
||||
[2m2026-05-29 19:17:31.761[0;39m [31m-ERROR[0;39m [35m7760[0;39m [31m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.web.embedded.tomcat.TomcatStarter [0;39m [2m:[0;39m Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 1: Error creating bean with name 'jerseyConfig': Invocation of init method failed
|
||||
[2m2026-05-29 19:17:31.812[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.apache.catalina.core.StandardService [0;39m [2m:[0;39m Stopping service [Tomcat]
|
||||
[2m2026-05-29 19:17:31.817[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.a.c.loader.WebappClassLoaderBase [0;39m [2m:[0;39m The web application [fineract-provider] appears to have started a thread named [HikariPool-1:housekeeper] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
|
||||
java.base/jdk.internal.misc.Unsafe.park(Native Method)
|
||||
java.base/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)
|
||||
java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1797)
|
||||
java.base/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182)
|
||||
java.base/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)
|
||||
java.base/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1070)
|
||||
java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
|
||||
java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||
java.base/java.lang.Thread.run(Thread.java:1583)
|
||||
[2m2026-05-29 19:17:31.820[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mConfigServletWebServerApplicationContext[0;39m [2m:[0;39m Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server
|
||||
[2m2026-05-29 19:17:31.820[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Shutdown initiated...
|
||||
[2m2026-05-29 19:17:31.822[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Shutdown completed.
|
||||
[2m2026-05-29 19:17:31.847[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.b.a.l.ConditionEvaluationReportLogger[0;39m [2m:[0;39m
|
||||
|
||||
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
|
||||
[2m2026-05-29 19:17:31.875[0;39m [31m-ERROR[0;39m [35m7760[0;39m [31m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.boot.SpringApplication [0;39m [2m:[0;39m Application run failed
|
||||
|
||||
org.springframework.context.ApplicationContextException: Unable to start web server
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:170)
|
||||
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
|
||||
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)
|
||||
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)
|
||||
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)
|
||||
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:149)
|
||||
at org.apache.fineract.ServerApplication.main(ServerApplication.java:57)
|
||||
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:147)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:107)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:517)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:219)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:193)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:167)
|
||||
... 7 common frames omitted
|
||||
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 1: Error creating bean with name 'jerseyConfig': Invocation of init method failed
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:413)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
|
||||
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:230)
|
||||
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:221)
|
||||
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:106)
|
||||
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:95)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:271)
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:245)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:52)
|
||||
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4452)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1201)
|
||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1191)
|
||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
|
||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:81)
|
||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:747)
|
||||
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:770)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1201)
|
||||
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1191)
|
||||
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
|
||||
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:81)
|
||||
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
|
||||
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:747)
|
||||
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:201)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:410)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:868)
|
||||
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
|
||||
at org.apache.catalina.startup.Tomcat.start(Tomcat.java:436)
|
||||
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:128)
|
||||
... 12 common frames omitted
|
||||
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jerseyConfig': Invocation of init method failed
|
||||
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:222)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:429)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1818)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
|
||||
... 61 common frames omitted
|
||||
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'journalEntriesApiResource' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\accounting\journalentry\api\JournalEntriesApiResource.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'springSecurityPlatformSecurityContext' defined in URL [jar:file:/C:/Users/adity/Music/CBS/fineract/fineract-security/build/libs/fineract-security-1.15.0-SNAPSHOT.jar!/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'configurationDomainServiceJpa' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\infrastructure\configuration\domain\ConfigurationDomainServiceJpa.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'permissionRepository' defined in org.apache.fineract.useradministration.domain.PermissionRepository defined in @EnableJpaRepositories declared on JPAConfig: Cannot resolve reference to bean 'jpaSharedEM_entityManagerFactory' while setting bean property 'entityManager'
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansWithAnnotation(DefaultListableBeanFactory.java:796)
|
||||
at org.springframework.context.support.AbstractApplicationContext.getBeansWithAnnotation(AbstractApplicationContext.java:1440)
|
||||
at org.apache.fineract.infrastructure.core.jersey.JerseyConfig.setup(JerseyConfig.java:58)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMethod.invoke(InitDestroyAnnotationBeanPostProcessor.java:457)
|
||||
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:401)
|
||||
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:219)
|
||||
... 74 common frames omitted
|
||||
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springSecurityPlatformSecurityContext' defined in URL [jar:file:/C:/Users/adity/Music/CBS/fineract/fineract-security/build/libs/fineract-security-1.15.0-SNAPSHOT.jar!/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'configurationDomainServiceJpa' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\infrastructure\configuration\domain\ConfigurationDomainServiceJpa.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'permissionRepository' defined in org.apache.fineract.useradministration.domain.PermissionRepository defined in @EnableJpaRepositories declared on JPAConfig: Cannot resolve reference to bean 'jpaSharedEM_entityManagerFactory' while setting bean property 'entityManager'
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
|
||||
... 91 common frames omitted
|
||||
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'configurationDomainServiceJpa' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\infrastructure\configuration\domain\ConfigurationDomainServiceJpa.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'permissionRepository' defined in org.apache.fineract.useradministration.domain.PermissionRepository defined in @EnableJpaRepositories declared on JPAConfig: Cannot resolve reference to bean 'jpaSharedEM_entityManagerFactory' while setting bean property 'entityManager'
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
|
||||
... 105 common frames omitted
|
||||
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'permissionRepository' defined in org.apache.fineract.useradministration.domain.PermissionRepository defined in @EnableJpaRepositories declared on JPAConfig: Cannot resolve reference to bean 'jpaSharedEM_entityManagerFactory' while setting bean property 'entityManager'
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:377)
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1725)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1474)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:606)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1698)
|
||||
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
|
||||
... 119 common frames omitted
|
||||
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaSharedEM_entityManagerFactory': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:377)
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:691)
|
||||
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:513)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
|
||||
... 132 common frames omitted
|
||||
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tenantDatabaseUpgradeService' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\infrastructure\core\service\migration\TenantDatabaseUpgradeService.class]: Tenant upgrades had exceptions
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
|
||||
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
|
||||
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
|
||||
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
|
||||
... 144 common frames omitted
|
||||
Caused by: java.lang.RuntimeException: Tenant upgrades had exceptions
|
||||
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenants(TenantDatabaseUpgradeService.java:167)
|
||||
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.afterPropertiesSet(TenantDatabaseUpgradeService.java:93)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873)
|
||||
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822)
|
||||
... 153 common frames omitted
|
||||
|
||||
|
||||
> Task :fineract-provider:bootRun FAILED
|
||||
|
||||
[Incubating] Problems report is available at: file:///C:/Users/adity/Music/CBS/fineract/build/reports/problems/problems-report.html
|
||||
|
||||
FAILURE: Build failed with an exception.
|
||||
|
||||
* What went wrong:
|
||||
Execution failed for task ':fineract-provider:bootRun'.
|
||||
> Process 'command 'C:\Program Files\Eclipse Adoptium\jdk-21.0.9.10-hotspot\bin\java.exe'' finished with non-zero exit value 1
|
||||
|
||||
* Try:
|
||||
> Run with --stacktrace option to get the stack trace.
|
||||
> Run with --info or --debug option to get more log output.
|
||||
> Get more help at https://help.gradle.org.
|
||||
|
||||
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
|
||||
|
||||
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
|
||||
|
||||
For more on this, please refer to https://docs.gradle.org/8.14.3/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
|
||||
|
||||
BUILD FAILED in 1m 30s
|
||||
83 actionable tasks: 5 executed, 78 up-to-date
|
||||
Resource usage collection shutdown not awaited
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+11464
-14684
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+40386
-37900
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+21
@@ -600,6 +600,27 @@ public class AccountingProcessorHelper {
|
||||
createCreditJournalEntryForSavings(office, currencyCode, creditAccount, savingsId, transactionId, transactionDate, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a direct transfer journal entry between two savings-type accounts (savings or current),
|
||||
* each with their own product GL mapping and entity ID so the correct account number appears per entry.
|
||||
*
|
||||
* @param debitProductId product ID whose SAVINGS_CONTROL GL is debited
|
||||
* @param debitEntityId entity (account) ID shown on the debit entry
|
||||
* @param creditProductId product ID whose SAVINGS_CONTROL GL is credited
|
||||
* @param creditEntityId entity (account) ID shown on the credit entry
|
||||
*/
|
||||
public void createDirectTransferJournalEntriesForSavings(final Office office, final String currencyCode,
|
||||
final Long debitProductId, final Long debitEntityId,
|
||||
final Long creditProductId, final Long creditEntityId,
|
||||
final String transactionId, final LocalDate transactionDate, final BigDecimal amount) {
|
||||
final GLAccount debitAccount = getLinkedGLAccountForSavingsProduct(debitProductId,
|
||||
CashAccountsForSavings.SAVINGS_CONTROL.getValue(), null);
|
||||
final GLAccount creditAccount = getLinkedGLAccountForSavingsProduct(creditProductId,
|
||||
CashAccountsForSavings.SAVINGS_CONTROL.getValue(), null);
|
||||
createDebitJournalEntryForSavings(office, currencyCode, debitAccount, debitEntityId, transactionId, transactionDate, amount);
|
||||
createCreditJournalEntryForSavings(office, currencyCode, creditAccount, creditEntityId, transactionId, transactionDate, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that creates a pair of related Debits and Credits for Cash Based accounting.
|
||||
*
|
||||
|
||||
+11
-8
@@ -106,8 +106,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
.append(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
|
||||
.append(" eao.external_id as externalAssetOwner, ")
|
||||
.append(" CASE ")
|
||||
.append(" WHEN journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id LIKE 'CA%' THEN ca.account_no ")
|
||||
.append(" WHEN journalEntry.entity_type_enum = 2 THEN sa.account_no ")
|
||||
.append(" WHEN journalEntry.entity_type_enum = 2 THEN COALESCE(ca.account_no, sa.account_no) ")
|
||||
.append(" ELSE NULL ")
|
||||
.append(" END as savingsAccountNo ");
|
||||
if (associationParametersData.isRunningBalanceRequired()) {
|
||||
@@ -130,8 +129,8 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
.append(" join m_currency curr on curr.code = journalEntry.currency_code ")
|
||||
.append(" left join m_external_asset_owner_journal_entry_mapping eajem on eajem.journal_entry_id = journalEntry.id ")
|
||||
.append(" left join m_external_asset_owner eao on eao.id = eajem.owner_id ")
|
||||
.append(" left join m_savings_account sa on sa.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id NOT LIKE 'CA%' ")
|
||||
.append(" left join m_current_account ca on ca.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id LIKE 'CA%' ");
|
||||
.append(" left join m_savings_account sa on sa.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 ")
|
||||
.append(" left join m_current_account ca on ca.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 ");
|
||||
if (associationParametersData.isTransactionDetailsRequired()) {
|
||||
sb.append(" left join m_loan_transaction as lt on journalEntry.loan_transaction_id = lt.id ")
|
||||
.append(" left join m_savings_account_transaction as st on journalEntry.savings_transaction_id = st.id ")
|
||||
@@ -265,6 +264,10 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
int arrayPos = 0;
|
||||
String whereClose = " where ";
|
||||
|
||||
// Exclude intermediary/reversed entries from all GL views
|
||||
sqlBuilder.append(whereClose).append("(journalEntry.reversed IS NULL OR journalEntry.reversed = false)");
|
||||
whereClose = " and ";
|
||||
|
||||
if (StringUtils.isNotBlank(transactionId)) {
|
||||
sqlBuilder.append(whereClose).append(" journalEntry.transaction_id = ?");
|
||||
objectArray[arrayPos] = transactionId;
|
||||
@@ -581,7 +584,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
"SELECT COALESCE(SUM(CASE WHEN je.type_enum = 2 THEN je.amount ELSE 0 END), 0) AS ob_debit, "
|
||||
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS ob_credit "
|
||||
+ "FROM acc_gl_journal_entry je "
|
||||
+ "WHERE je.account_id = ? AND je.entry_date < ?");
|
||||
+ "WHERE je.account_id = ? AND je.entry_date < ? AND (je.reversed IS NULL OR je.reversed = false)");
|
||||
obParams.add(glAccountId);
|
||||
obParams.add(fromDate);
|
||||
if (officeId != null && officeId > 0) {
|
||||
@@ -608,7 +611,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS dw_credit, "
|
||||
+ "COUNT(*) AS dw_count "
|
||||
+ "FROM acc_gl_journal_entry je "
|
||||
+ "WHERE 1=1");
|
||||
+ "WHERE (je.reversed IS NULL OR je.reversed = false)");
|
||||
if (glAccountId != null && glAccountId > 0) {
|
||||
dwSql.append(" AND je.account_id = ?");
|
||||
dwParams.add(glAccountId);
|
||||
@@ -684,7 +687,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
final StringBuilder totSql = new StringBuilder(
|
||||
"SELECT COALESCE(SUM(CASE WHEN je.type_enum = 2 THEN je.amount ELSE 0 END), 0) AS totalDebit, "
|
||||
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS totalCredit "
|
||||
+ "FROM acc_gl_journal_entry je WHERE 1=1");
|
||||
+ "FROM acc_gl_journal_entry je WHERE (je.reversed IS NULL OR je.reversed = false)");
|
||||
if (glAccountId != null && glAccountId > 0) {
|
||||
totSql.append(" AND je.account_id = ?");
|
||||
totParams.add(glAccountId);
|
||||
@@ -723,7 +726,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
{
|
||||
final List<Object> cntParams = new ArrayList<>();
|
||||
final StringBuilder cntSql = new StringBuilder(
|
||||
"SELECT COUNT(*) FROM acc_gl_journal_entry je WHERE 1=1");
|
||||
"SELECT COUNT(*) FROM acc_gl_journal_entry je WHERE (je.reversed IS NULL OR je.reversed = false)");
|
||||
if (glAccountId != null && glAccountId > 0) {
|
||||
cntSql.append(" AND je.account_id = ?");
|
||||
cntParams.add(glAccountId);
|
||||
|
||||
+8
@@ -146,6 +146,14 @@ public class SecurityConfig {
|
||||
auth.requestMatchers(API_MATCHER.matcher(HttpMethod.OPTIONS, "/api/**")).permitAll()
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/echo")).permitAll()
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/authentication")).permitAll()
|
||||
// Meeting vote public endpoints — secured by UUID token in the URL path
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/public/meeting-vote/**")).permitAll()
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/public/meeting-vote/**")).permitAll()
|
||||
// IMPS receiver — Fineract acts as the bank-side endpoint under
|
||||
// IMPS_BANK_API_SPEC_CBS_1.0 §6.2.x. LincPay (the switch) calls these without
|
||||
// a Fineract Bearer token; security is enforced at the JWT envelope
|
||||
// (signature + encryption) by ImpsJwtUtil / ImpsCryptoUtil inside the handler.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/imps/**")).permitAll()
|
||||
// Login OTP — same posture as PAN/GSTIN public verification: provider-credentialed,
|
||||
// no Fineract auth required.
|
||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/login/otp/generate")).permitAll()
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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.imps.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.function.Function;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.imps.crypto.ImpsCryptoUtil;
|
||||
import org.apache.fineract.infrastructure.imps.crypto.ImpsEnvelope;
|
||||
import org.apache.fineract.infrastructure.imps.crypto.ImpsJwtUtil;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsRequestData;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsResponseData;
|
||||
import org.apache.fineract.infrastructure.imps.service.ImpsCbsService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Receiver-side IMPS CBS endpoints — Fineract acting as the bank under
|
||||
* IMPS_BANK_API_SPEC_CBS_1.0 §6. Each endpoint:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Reads the {@link ImpsEnvelope} ({@code data}, {@code signature}).</li>
|
||||
* <li>Verifies the inbound JWT via {@link ImpsJwtUtil}.</li>
|
||||
* <li>Decrypts {@code data} via {@link ImpsCryptoUtil}.</li>
|
||||
* <li>Hands the typed {@link ImpsRequestData} to {@link ImpsCbsService}.</li>
|
||||
* <li>Encrypts the response, signs an outbound JWT, returns the envelope.</li>
|
||||
* </ol>
|
||||
*/
|
||||
@Path("/v1/imps")
|
||||
@Component
|
||||
@Tag(name = "IMPS CBS (Bank Side)", description = "Receiver-side endpoints for IMPS_BANK_API_SPEC_CBS_1.0 §6.2.")
|
||||
@RequiredArgsConstructor
|
||||
public class ImpsCbsApiResource {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ImpsCbsApiResource.class);
|
||||
|
||||
private final ImpsCryptoUtil cryptoUtil;
|
||||
private final ImpsJwtUtil jwtUtil;
|
||||
private final ImpsCbsService cbsService;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@POST
|
||||
@Path("/ReqPay/v1/urn:txnid:{txnid}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "IMPS ReqPay (§6.2.1)")
|
||||
public Response reqPay(@PathParam("txnid") String txnid, ImpsEnvelope body) {
|
||||
return handle("ReqPay", txnid, body, cbsService::reqPay);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/ReqValAdd/v1/urn:txnid:{txnid}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "IMPS ReqValAdd (§6.2.3)")
|
||||
public Response reqValAdd(@PathParam("txnid") String txnid, ImpsEnvelope body) {
|
||||
return handle("ReqValAdd", txnid, body, cbsService::reqValAdd);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/ReqChkTxn/v1/urn:txnid:{txnid}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "IMPS ReqChkTxn (§6.2.2)")
|
||||
public Response reqChkTxn(@PathParam("txnid") String txnid, ImpsEnvelope body) {
|
||||
return handle("ReqChkTxn", txnid, body, cbsService::reqChkTxn);
|
||||
}
|
||||
|
||||
private Response handle(String api, String txnid, ImpsEnvelope body, Function<ImpsRequestData, ImpsResponseData> handler) {
|
||||
log.info("[ImpsCbs] Inbound {} txnid={}", api, txnid);
|
||||
try {
|
||||
if (!jwtUtil.verifyJwtSignature(body.getSignature())) {
|
||||
log.warn("[ImpsCbs] {} txnid={} — JWT verification FAILED", api, txnid);
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(new ImpsEnvelope("invalid-signature", "")).build();
|
||||
}
|
||||
|
||||
String decrypted = cryptoUtil.decryptJson(body.getData());
|
||||
if (decrypted == null) {
|
||||
log.warn("[ImpsCbs] {} txnid={} — decrypt FAILED", api, txnid);
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(new ImpsEnvelope("decrypt-failed", "")).build();
|
||||
}
|
||||
|
||||
ImpsRequestData request = objectMapper.readValue(decrypted, ImpsRequestData.class);
|
||||
ImpsResponseData response = handler.apply(request);
|
||||
String respJson = objectMapper.writeValueAsString(response);
|
||||
log.info("[ImpsCbs] {} txnid={} responding status={} responseCode={}", api, txnid, response.getStatus(),
|
||||
response.getResponseCode());
|
||||
|
||||
String encResp = cryptoUtil.encryptJson(respJson);
|
||||
String jwt = jwtUtil.signPayloadAsJwt(respJson);
|
||||
return Response.ok(new ImpsEnvelope(encResp, jwt)).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[ImpsCbs] {} txnid={} unhandled error: {}", api, txnid, e.toString(), e);
|
||||
return Response.serverError().entity(new ImpsEnvelope("error: " + e.getMessage(), "")).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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.imps.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.apache.fineract.infrastructure.imps.crypto.ImpsCryptoUtil;
|
||||
import org.apache.fineract.infrastructure.imps.crypto.ImpsEnvelope;
|
||||
import org.apache.fineract.infrastructure.imps.crypto.ImpsJwtUtil;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsRequestData;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsResponseData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Outbound IMPS calls — Fineract acting as the bank initiating ReqPay / ReqValAdd /
|
||||
* ReqChkTxn against the LincPay switch.
|
||||
*
|
||||
* <p>Each call: build {@link ImpsRequestData} → encrypt with LincPay public key →
|
||||
* sign JWT with CBS private key → POST envelope → verify response JWT → decrypt
|
||||
* response body → parse to {@link ImpsResponseData}.
|
||||
*
|
||||
* <p>The crypto utilities are symmetric across inbound and outbound: encryptJson
|
||||
* uses the peer's public key (LincPay), decryptJson uses our private key (CBS).
|
||||
* No additional key configuration is needed beyond what's already set for the
|
||||
* inbound receiver.
|
||||
*/
|
||||
@Service
|
||||
public class ImpsSwitchClient {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ImpsSwitchClient.class);
|
||||
private static final MediaType JSON = MediaType.get("application/json");
|
||||
|
||||
@Value("${fineract.imps.switch.base-url:}")
|
||||
private String baseUrl;
|
||||
|
||||
@Value("${fineract.imps.switch.timeout-ms:10000}")
|
||||
private int timeoutMs;
|
||||
|
||||
private final ImpsCryptoUtil cryptoUtil;
|
||||
private final ImpsJwtUtil jwtUtil;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private OkHttpClient httpClient;
|
||||
|
||||
public ImpsSwitchClient(ImpsCryptoUtil cryptoUtil, ImpsJwtUtil jwtUtil) {
|
||||
this.cryptoUtil = cryptoUtil;
|
||||
this.jwtUtil = jwtUtil;
|
||||
}
|
||||
|
||||
private OkHttpClient client() {
|
||||
if (httpClient == null) {
|
||||
httpClient = new OkHttpClient.Builder().connectTimeout(Duration.ofMillis(timeoutMs))
|
||||
.readTimeout(timeoutMs, TimeUnit.MILLISECONDS).writeTimeout(timeoutMs, TimeUnit.MILLISECONDS).build();
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public ImpsResponseData reqPay(String txnId, ImpsRequestData payload) {
|
||||
return invoke("IMPS", "ReqPay", txnId, payload);
|
||||
}
|
||||
|
||||
public ImpsResponseData reqValAdd(String txnId, ImpsRequestData payload) {
|
||||
return invoke("IMPS", "ReqValAdd", txnId, payload);
|
||||
}
|
||||
|
||||
public ImpsResponseData reqChkTxn(String txnId, ImpsRequestData payload) {
|
||||
return invoke("IMPS", "ReqChkTxn", txnId, payload);
|
||||
}
|
||||
|
||||
public ImpsResponseData neftPay(String txnId, ImpsRequestData payload) {
|
||||
return invoke("NEFT", "ReqPay", txnId, payload);
|
||||
}
|
||||
|
||||
public ImpsResponseData neftChkTxn(String txnId, ImpsRequestData payload) {
|
||||
return invoke("NEFT", "ReqChkTxn", txnId, payload);
|
||||
}
|
||||
|
||||
public ImpsResponseData rtgsPay(String txnId, ImpsRequestData payload) {
|
||||
return invoke("RTGS", "ReqPay", txnId, payload);
|
||||
}
|
||||
|
||||
public ImpsResponseData rtgsChkTxn(String txnId, ImpsRequestData payload) {
|
||||
return invoke("RTGS", "ReqChkTxn", txnId, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort recall on the switch. Real-world semantics vary by channel:
|
||||
* IMPS uses a dispute / chargeback flow rather than a synchronous recall;
|
||||
* NEFT supports {@code ReqDirectRet} for return-on-no-credit; RTGS recall
|
||||
* is rare and usually manual. Callers should treat exceptions as soft
|
||||
* failures and proceed with local reversal.
|
||||
*/
|
||||
public ImpsResponseData recall(String channel, String txnId, ImpsRequestData payload) {
|
||||
return invoke(channel, "ReqRecall", txnId, payload);
|
||||
}
|
||||
|
||||
private ImpsResponseData invoke(String channel, String api, String txnId, ImpsRequestData payload) {
|
||||
if (baseUrl == null || baseUrl.isBlank()) {
|
||||
throw new IllegalStateException("fineract.imps.switch.base-url is not set");
|
||||
}
|
||||
try {
|
||||
String plainBody = objectMapper.writeValueAsString(payload);
|
||||
String encrypted = cryptoUtil.encryptJson(plainBody);
|
||||
String signature = jwtUtil.signPayloadAsJwt(plainBody);
|
||||
ImpsEnvelope env = new ImpsEnvelope(encrypted, signature);
|
||||
String envJson = objectMapper.writeValueAsString(env);
|
||||
|
||||
String url = baseUrl.replaceAll("/+$", "") + "/" + channel + "/" + api + "/v1/urn:txnid:" + txnId;
|
||||
log.info("[ImpsSwitchClient] {} {} txnId={} → POST {}", channel, api, txnId, url);
|
||||
|
||||
Request req = new Request.Builder().url(url).post(RequestBody.create(envJson, JSON)).build();
|
||||
try (Response resp = client().newCall(req).execute()) {
|
||||
String respBody = bodyAsString(resp.body());
|
||||
if (!resp.isSuccessful()) {
|
||||
log.warn("[ImpsSwitchClient] {} {} txnId={} HTTP {} body={}", channel, api, txnId, resp.code(), respBody);
|
||||
throw new IOException("Switch returned HTTP " + resp.code());
|
||||
}
|
||||
|
||||
ImpsEnvelope respEnv = objectMapper.readValue(respBody, ImpsEnvelope.class);
|
||||
if (!jwtUtil.verifyJwtSignature(respEnv.getSignature())) {
|
||||
throw new IllegalStateException("Response JWT signature verification failed");
|
||||
}
|
||||
String decrypted = cryptoUtil.decryptJson(respEnv.getData());
|
||||
if (decrypted == null) {
|
||||
throw new IllegalStateException("Response payload decrypt failed");
|
||||
}
|
||||
log.info("[ImpsSwitchClient] {} {} txnId={} ← decrypted response", channel, api, txnId);
|
||||
return objectMapper.readValue(decrypted, ImpsResponseData.class);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(channel + " " + api + " call failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String bodyAsString(ResponseBody body) throws IOException {
|
||||
return body == null ? "" : body.string();
|
||||
}
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* 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.imps.crypto;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.util.Base64;
|
||||
import java.util.Enumeration;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Encryption / decryption matching IMPS_BANK_API_SPEC_CBS_1.0 §4.3.
|
||||
*
|
||||
* <p>Wire format: {@code <base64(wrappedAESKey)>:<base64(iv)>:<base64(cipherText)>}.
|
||||
* AES-256-GCM with a 12-byte IV and a 128-bit tag; the AES key is wrapped via
|
||||
* RSA-OAEP-SHA256.
|
||||
*
|
||||
* <h3>Key directions</h3>
|
||||
* <ul>
|
||||
* <li><b>Inbound</b>: {@link #decryptJson(String)} — unwrap the AES key with
|
||||
* the CBS private key loaded from {@code fineract.imps.cbs.private-key-path}.</li>
|
||||
* <li><b>Outbound</b>: {@link #encryptJson(String)} — wrap the AES key with
|
||||
* the LincPay public key loaded from
|
||||
* {@code fineract.imps.lincpay.public-key-path}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Keys are loaded once at startup in {@link #init()}.
|
||||
*/
|
||||
@Component
|
||||
public class ImpsCryptoUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ImpsCryptoUtil.class);
|
||||
|
||||
private static final String AES = "AES";
|
||||
private static final String AES_GCM = "AES/GCM/NoPadding";
|
||||
private static final String RSA = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
|
||||
private static final int GCM_TAG_LENGTH = 128;
|
||||
private static final int AES_KEY_SIZE = 256;
|
||||
private static final int IV_SIZE = 12;
|
||||
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
@Value("${fineract.imps.cbs.private-key-path:}")
|
||||
private String cbsPrivateKeyPath;
|
||||
|
||||
@Value("${fineract.imps.cbs.private-key-password:}")
|
||||
private String cbsPrivateKeyPassword;
|
||||
|
||||
@Value("${fineract.imps.lincpay.public-key-path:}")
|
||||
private String lincpayPublicKeyPath;
|
||||
|
||||
private PrivateKey cbsPrivateKey;
|
||||
private PublicKey lincpayPublicKey;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
if (cbsPrivateKeyPath == null || cbsPrivateKeyPath.isBlank()) {
|
||||
log.info("[ImpsCryptoUtil] fineract.imps.cbs.private-key-path not set — CBS private key NOT loaded");
|
||||
} else {
|
||||
try {
|
||||
cbsPrivateKey = loadFirstPrivateKey(cbsPrivateKeyPath, cbsPrivateKeyPassword);
|
||||
log.info("[ImpsCryptoUtil] CBS private key loaded from {}", cbsPrivateKeyPath);
|
||||
} catch (Exception e) {
|
||||
log.error("[ImpsCryptoUtil] Failed to load CBS private key from {}: {}", cbsPrivateKeyPath, e.toString());
|
||||
}
|
||||
}
|
||||
if (lincpayPublicKeyPath == null || lincpayPublicKeyPath.isBlank()) {
|
||||
log.info("[ImpsCryptoUtil] fineract.imps.lincpay.public-key-path not set — LincPay public key NOT loaded");
|
||||
} else {
|
||||
try {
|
||||
lincpayPublicKey = loadPublicKey(lincpayPublicKeyPath);
|
||||
log.info("[ImpsCryptoUtil] LincPay public key loaded from {}", lincpayPublicKeyPath);
|
||||
} catch (Exception e) {
|
||||
log.error("[ImpsCryptoUtil] Failed to load LincPay public key from {}: {}", lincpayPublicKeyPath, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Decrypt inbound payload using the CBS private key. */
|
||||
public String decryptJson(String encryptedPayload) {
|
||||
if (cbsPrivateKey == null) {
|
||||
throw new IllegalStateException("CBS private key not loaded — check fineract.imps.cbs.private-key-path");
|
||||
}
|
||||
try {
|
||||
String[] parts = encryptedPayload.split(":");
|
||||
if (parts.length != 3) {
|
||||
log.error("Invalid encrypted payload format — expected 3 colon-separated parts");
|
||||
return null;
|
||||
}
|
||||
byte[] iv = Base64.getDecoder().decode(parts[1]);
|
||||
SecretKey aesKey = unwrapAESKey(parts[0], cbsPrivateKey);
|
||||
if (aesKey == null) {
|
||||
return null;
|
||||
}
|
||||
return decryptData(parts[2], aesKey, iv);
|
||||
} catch (Exception e) {
|
||||
log.error("Error decrypting inbound payload", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Encrypt outbound payload using the LincPay public key. */
|
||||
public String encryptJson(String json) {
|
||||
if (lincpayPublicKey == null) {
|
||||
throw new IllegalStateException("LincPay public key not loaded — check fineract.imps.lincpay.public-key-path");
|
||||
}
|
||||
try {
|
||||
SecretKey aesKey = generateAESKey();
|
||||
if (aesKey == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] iv = generateIV();
|
||||
String cipherText = encryptData(json, aesKey, iv);
|
||||
String wrappedKey = wrapAESKey(aesKey, lincpayPublicKey);
|
||||
if (cipherText == null || wrappedKey == null) {
|
||||
return null;
|
||||
}
|
||||
return wrappedKey + ":" + Base64.getEncoder().encodeToString(iv) + ":" + cipherText;
|
||||
} catch (Exception e) {
|
||||
log.error("Error encrypting outbound payload", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Exposed for {@link ImpsJwtUtil} — sign outbound JWT with the same CBS private key. */
|
||||
public PrivateKey getCbsPrivateKey() {
|
||||
return cbsPrivateKey;
|
||||
}
|
||||
|
||||
/** Exposed for {@link ImpsJwtUtil} — verify inbound JWT with the LincPay public key. */
|
||||
public PublicKey getLincpayPublicKey() {
|
||||
return lincpayPublicKey;
|
||||
}
|
||||
|
||||
private static SecretKey generateAESKey() throws Exception {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance(AES);
|
||||
keyGen.init(AES_KEY_SIZE);
|
||||
return keyGen.generateKey();
|
||||
}
|
||||
|
||||
private static byte[] generateIV() {
|
||||
byte[] iv = new byte[IV_SIZE];
|
||||
SECURE_RANDOM.nextBytes(iv);
|
||||
return iv;
|
||||
}
|
||||
|
||||
private static String wrapAESKey(SecretKey aesKey, PublicKey publicKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(RSA);
|
||||
cipher.init(Cipher.WRAP_MODE, publicKey);
|
||||
return Base64.getEncoder().encodeToString(cipher.wrap(aesKey));
|
||||
} catch (Exception e) {
|
||||
log.error("Error wrapping AES key", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static SecretKey unwrapAESKey(String wrappedBase64, PrivateKey privateKey) {
|
||||
try {
|
||||
byte[] wrapped = Base64.getDecoder().decode(wrappedBase64);
|
||||
Cipher cipher = Cipher.getInstance(RSA);
|
||||
cipher.init(Cipher.UNWRAP_MODE, privateKey);
|
||||
return (SecretKey) cipher.unwrap(wrapped, AES, Cipher.SECRET_KEY);
|
||||
} catch (Exception e) {
|
||||
log.error("Error unwrapping AES key", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String encryptData(String plain, SecretKey aesKey, byte[] iv) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(GCM_TAG_LENGTH, iv));
|
||||
byte[] enc = cipher.doFinal(plain.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(enc);
|
||||
} catch (Exception e) {
|
||||
log.error("Error encrypting data", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String decryptData(String encBase64, SecretKey aesKey, byte[] iv) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
cipher.init(Cipher.DECRYPT_MODE, aesKey, new GCMParameterSpec(GCM_TAG_LENGTH, iv));
|
||||
byte[] dec = cipher.doFinal(Base64.getDecoder().decode(encBase64));
|
||||
return new String(dec, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
log.error("Error decrypting data", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static PrivateKey loadFirstPrivateKey(String p12Path, String password) throws Exception {
|
||||
char[] pwd = password != null ? password.toCharArray() : null;
|
||||
try (FileInputStream fis = new FileInputStream(p12Path)) {
|
||||
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||
ks.load(fis, pwd);
|
||||
Enumeration<String> aliases = ks.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
String alias = aliases.nextElement();
|
||||
if (ks.isKeyEntry(alias)) {
|
||||
return (PrivateKey) ks.getKey(alias, pwd);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("No private-key entry in keystore at " + p12Path);
|
||||
}
|
||||
}
|
||||
|
||||
private static PublicKey loadPublicKey(String crtPath) throws Exception {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
try (InputStream is = new BufferedInputStream(new FileInputStream(crtPath))) {
|
||||
Certificate cert = cf.generateCertificate(is);
|
||||
return cert.getPublicKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.infrastructure.imps.crypto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Wire envelope shared with the LincPay IMPS gateway:
|
||||
* {@code { "data": "<encrypted JSON>", "signature": "<RS256 JWT>" } }.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ImpsEnvelope {
|
||||
|
||||
private String data;
|
||||
private String signature;
|
||||
}
|
||||
+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.infrastructure.imps.crypto;
|
||||
|
||||
import com.nimbusds.jose.JOSEObjectType;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jose.JWSSigner;
|
||||
import com.nimbusds.jose.JWSVerifier;
|
||||
import com.nimbusds.jose.crypto.RSASSASigner;
|
||||
import com.nimbusds.jose.crypto.RSASSAVerifier;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.Date;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* JWT helpers for IMPS_BANK_API_SPEC_CBS_1.0 §4.2.
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>Verify</b> inbound LincPay-signed JWT using the LincPay public key
|
||||
* loaded by {@link ImpsCryptoUtil}.</li>
|
||||
* <li><b>Sign</b> outbound JWT (RS256, 1h TTL, payload in {@code "data"}
|
||||
* claim) using the CBS private key loaded by {@link ImpsCryptoUtil}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>{@link #verifyJwtSignature(String)} returns {@code true} when the LincPay
|
||||
* public key is not configured — useful for smoke testing the encrypt/decrypt
|
||||
* path before both certs are wired up.
|
||||
*/
|
||||
@Component
|
||||
public class ImpsJwtUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ImpsJwtUtil.class);
|
||||
|
||||
private static final long JWT_TTL_MILLIS = 3600_000L;
|
||||
|
||||
private final ImpsCryptoUtil cryptoUtil;
|
||||
|
||||
public ImpsJwtUtil(ImpsCryptoUtil cryptoUtil) {
|
||||
this.cryptoUtil = cryptoUtil;
|
||||
}
|
||||
|
||||
/** Sign a JSON payload — payload carried in the {@code "data"} claim. */
|
||||
public String signPayloadAsJwt(String jsonPayload) {
|
||||
PrivateKey privateKey = cryptoUtil.getCbsPrivateKey();
|
||||
if (privateKey == null) {
|
||||
log.warn("[ImpsJwtUtil] CBS private key not loaded — outbound JWT signing SKIPPED");
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
JWSSigner signer = new RSASSASigner(privateKey);
|
||||
JWTClaimsSet claims = new JWTClaimsSet.Builder().claim("data", jsonPayload).issueTime(new Date())
|
||||
.expirationTime(new Date(System.currentTimeMillis() + JWT_TTL_MILLIS)).build();
|
||||
SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build(), claims);
|
||||
signedJWT.sign(signer);
|
||||
return signedJWT.serialize();
|
||||
} catch (Exception e) {
|
||||
log.error("[ImpsJwtUtil] Error signing outbound JWT: {}", e.toString(), e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/** Verify an inbound JWT against the LincPay public key. */
|
||||
public boolean verifyJwtSignature(String jwtToken) {
|
||||
if (jwtToken == null || jwtToken.isBlank()) {
|
||||
log.warn("[ImpsJwtUtil] Inbound JWT is blank — verification skipped (signature missing)");
|
||||
return true;
|
||||
}
|
||||
PublicKey pub = cryptoUtil.getLincpayPublicKey();
|
||||
if (pub == null) {
|
||||
log.warn("[ImpsJwtUtil] LincPay public key not loaded — verification skipped (DEV MODE)");
|
||||
return true;
|
||||
}
|
||||
if (!(pub instanceof RSAPublicKey rsaPub)) {
|
||||
log.error("[ImpsJwtUtil] LincPay public key is not RSA (type={}) — verification FAILED", pub.getAlgorithm());
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
SignedJWT signedJWT = SignedJWT.parse(jwtToken);
|
||||
JWSVerifier verifier = new RSASSAVerifier(rsaPub);
|
||||
return signedJWT.verify(verifier);
|
||||
} catch (Exception e) {
|
||||
log.error("[ImpsJwtUtil] Verification error: {}", e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.infrastructure.imps.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/** Account block under {@code payer.account} / {@code payee.account}. */
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class AccountData {
|
||||
|
||||
private String accountNumber;
|
||||
private String ifsc;
|
||||
private String accountType;
|
||||
}
|
||||
+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.infrastructure.imps.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Union of the decrypted inbound request fields across the three IMPS APIs
|
||||
* (ReqPay §6.2.1, ReqValAdd §6.2.3, ReqChkTxn §6.2.2). Fields not present in a
|
||||
* given API stay {@code null}.
|
||||
*
|
||||
* <p>Covers the fields the simulator extracted via {@code JsonNode} dotted paths.
|
||||
* Extend with additional spec fields (e.g. {@code remarks}, {@code merchantCode},
|
||||
* {@code signature}) as the IMPS_BANK_API_SPEC_CBS_1.0 §6 schemas are consulted.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ImpsRequestData {
|
||||
|
||||
private String txnId;
|
||||
private String orgTxnId;
|
||||
private String refId;
|
||||
private String utr;
|
||||
private String amount;
|
||||
private PartyData payer;
|
||||
private PartyData payee;
|
||||
}
|
||||
+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.infrastructure.imps.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Union of CBS response fields across RespPay / RespValAdd / RespChkTxn per
|
||||
* IMPS_BANK_API_SPEC_CBS_1.0 §6.2.x. {@code @JsonInclude(NON_NULL)} so each API
|
||||
* only emits the fields its spec section requires.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ImpsResponseData {
|
||||
|
||||
private String txnId;
|
||||
private String orgTxnId;
|
||||
private String refId;
|
||||
private String timeStamp;
|
||||
private String utr;
|
||||
private String amount;
|
||||
private String status;
|
||||
private String responseCode;
|
||||
private String accountNumber;
|
||||
private String ifsc;
|
||||
private String maskName;
|
||||
}
|
||||
+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.infrastructure.imps.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/** Payer / payee block on an IMPS request. */
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class PartyData {
|
||||
|
||||
private String name;
|
||||
private String mobile;
|
||||
private AccountData account;
|
||||
}
|
||||
+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.infrastructure.imps.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Lob;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||
|
||||
/**
|
||||
* One row per inbound IMPS request received by Fineract acting as the CBS.
|
||||
* Records audit + idempotency state — a retried {@code txnId} should resolve
|
||||
* back to the same {@code status} / {@code responseCode} via {@code unique(txn_id, api)}.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_imps_transaction")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ImpsTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
/** Which spec endpoint produced this row — REQ_PAY / REQ_VAL_ADD / REQ_CHK_TXN. */
|
||||
@Column(name = "api", nullable = false, length = 32)
|
||||
private String api;
|
||||
|
||||
@Column(name = "txn_id", nullable = false, length = 64)
|
||||
private String txnId;
|
||||
|
||||
@Column(name = "org_txn_id", length = 64)
|
||||
private String orgTxnId;
|
||||
|
||||
@Column(name = "ref_id", length = 64)
|
||||
private String refId;
|
||||
|
||||
@Column(name = "utr", length = 64)
|
||||
private String utr;
|
||||
|
||||
@Column(name = "amount", length = 32)
|
||||
private String amount;
|
||||
|
||||
@Column(name = "payee_account_number", length = 64)
|
||||
private String payeeAccountNumber;
|
||||
|
||||
@Column(name = "payee_ifsc", length = 32)
|
||||
private String payeeIfsc;
|
||||
|
||||
/** Resolved scenario — SUCCESS / DEEMED / FAILED. */
|
||||
@Column(name = "status", length = 16)
|
||||
private String status;
|
||||
|
||||
/** IMPS response code — 00 / 91 / XC / 8 / etc. */
|
||||
@Column(name = "response_code", length = 8)
|
||||
private String responseCode;
|
||||
|
||||
/** Decrypted request JSON, kept for audit / replay. */
|
||||
@Lob
|
||||
@Column(name = "request_json")
|
||||
private String requestJson;
|
||||
|
||||
/** Outbound response JSON before encryption, kept for audit. */
|
||||
@Lob
|
||||
@Column(name = "response_json")
|
||||
private String responseJson;
|
||||
|
||||
public static ImpsTransaction newRow(String api, String txnId) {
|
||||
ImpsTransaction t = new ImpsTransaction();
|
||||
t.api = api;
|
||||
t.txnId = txnId;
|
||||
return t;
|
||||
}
|
||||
}
|
||||
+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.infrastructure.imps.domain;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface ImpsTransactionRepository extends JpaRepository<ImpsTransaction, Long> {
|
||||
|
||||
Optional<ImpsTransaction> findFirstByApiAndTxnId(String api, String txnId);
|
||||
}
|
||||
+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.infrastructure.imps.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsRequestData;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsResponseData;
|
||||
|
||||
/**
|
||||
* Receiver-side IMPS handler. Each method takes the decrypted request and
|
||||
* returns the unencrypted response body — the controller layer is responsible
|
||||
* for envelope encryption / signing around these calls.
|
||||
*/
|
||||
public interface ImpsCbsService {
|
||||
|
||||
ImpsResponseData reqPay(ImpsRequestData request);
|
||||
|
||||
ImpsResponseData reqValAdd(ImpsRequestData request);
|
||||
|
||||
ImpsResponseData reqChkTxn(ImpsRequestData request);
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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.imps.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.imps.data.AccountData;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsRequestData;
|
||||
import org.apache.fineract.infrastructure.imps.data.ImpsResponseData;
|
||||
import org.apache.fineract.infrastructure.imps.data.PartyData;
|
||||
import org.apache.fineract.infrastructure.imps.domain.ImpsTransaction;
|
||||
import org.apache.fineract.infrastructure.imps.domain.ImpsTransactionRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* STUB implementation — returns hard-coded SUCCESS for every request and
|
||||
* persists an audit row per call. Replace the {@link #buildResponse} body
|
||||
* with real Fineract account lookup / debit-credit logic when that scope is
|
||||
* agreed.
|
||||
*
|
||||
* <p>Idempotency: a retried {@code (api, txnId)} short-circuits to the
|
||||
* previously-recorded response instead of running the handler twice.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ImpsCbsServiceImpl implements ImpsCbsService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ImpsCbsServiceImpl.class);
|
||||
private static final DateTimeFormatter ISO_OFFSET = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
||||
|
||||
private final ImpsTransactionRepository repository;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ImpsResponseData reqPay(ImpsRequestData request) {
|
||||
return handle("REQ_PAY", request, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ImpsResponseData reqValAdd(ImpsRequestData request) {
|
||||
return handle("REQ_VAL_ADD", request, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ImpsResponseData reqChkTxn(ImpsRequestData request) {
|
||||
return handle("REQ_CHK_TXN", request, false);
|
||||
}
|
||||
|
||||
private ImpsResponseData handle(String api, ImpsRequestData request, boolean echoIfsc) {
|
||||
Optional<ImpsTransaction> prior = repository.findFirstByApiAndTxnId(api, request.getTxnId());
|
||||
if (prior.isPresent()) {
|
||||
log.info("[ImpsCbsService] {} txnId={} idempotent replay — returning prior response", api, request.getTxnId());
|
||||
return readResponse(prior.get());
|
||||
}
|
||||
|
||||
ImpsResponseData response = buildResponse(api, request, echoIfsc);
|
||||
|
||||
ImpsTransaction row = ImpsTransaction.newRow(api, request.getTxnId());
|
||||
row.setOrgTxnId(request.getOrgTxnId());
|
||||
row.setRefId(request.getRefId());
|
||||
row.setUtr(request.getUtr());
|
||||
row.setAmount(request.getAmount());
|
||||
AccountData payeeAcc = Optional.ofNullable(request.getPayee()).map(PartyData::getAccount).orElse(null);
|
||||
if (payeeAcc != null) {
|
||||
row.setPayeeAccountNumber(payeeAcc.getAccountNumber());
|
||||
row.setPayeeIfsc(payeeAcc.getIfsc());
|
||||
}
|
||||
row.setStatus(response.getStatus());
|
||||
row.setResponseCode(response.getResponseCode());
|
||||
row.setRequestJson(writeJson(request));
|
||||
row.setResponseJson(writeJson(response));
|
||||
repository.save(row);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/** STUB — replace with real account lookup + debit/credit. */
|
||||
private ImpsResponseData buildResponse(String api, ImpsRequestData request, boolean echoIfsc) {
|
||||
ImpsResponseData resp = new ImpsResponseData();
|
||||
resp.setTxnId(request.getTxnId());
|
||||
resp.setOrgTxnId(request.getOrgTxnId());
|
||||
resp.setRefId(request.getRefId());
|
||||
resp.setUtr(request.getUtr());
|
||||
resp.setAmount(request.getAmount());
|
||||
resp.setTimeStamp(OffsetDateTime.now().format(ISO_OFFSET));
|
||||
resp.setStatus("SUCCESS");
|
||||
resp.setResponseCode("00");
|
||||
AccountData payeeAcc = Optional.ofNullable(request.getPayee()).map(PartyData::getAccount).orElse(null);
|
||||
if (payeeAcc != null) {
|
||||
resp.setAccountNumber(payeeAcc.getAccountNumber());
|
||||
if (echoIfsc) {
|
||||
resp.setIfsc(payeeAcc.getIfsc());
|
||||
}
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
private String writeJson(Object o) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(o);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("[ImpsCbsService] failed to serialize {} for audit: {}", o.getClass().getSimpleName(), e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ImpsResponseData readResponse(ImpsTransaction row) {
|
||||
if (row.getResponseJson() == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(row.getResponseJson(), ImpsResponseData.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("[ImpsCbsService] failed to deserialize stored response for txnId={}: {}", row.getTxnId(), e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -120,7 +120,7 @@ public class TellerManagementReadPlatformServiceImpl implements TellerManagement
|
||||
|
||||
try {
|
||||
final TellerMapper tm = new TellerMapper();
|
||||
final String sql = "select " + tm.schema() + " where t.id = ?";
|
||||
final String sql = "select " + tm.schema() + " where t.id = ? and t.is_deleted = false";
|
||||
|
||||
return this.jdbcTemplate.queryForObject(sql, tm, new Object[] { tellerId }); // NOSONAR
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
@@ -198,7 +198,7 @@ public class TellerManagementReadPlatformServiceImpl implements TellerManagement
|
||||
hierarchySearchString = hierarchy + "%";
|
||||
}
|
||||
final TellerMapper tm = new TellerMapper();
|
||||
final String sql = "select " + tm.schema() + "where o.hierarchy like ? order by o.hierarchy";
|
||||
final String sql = "select " + tm.schema() + "where o.hierarchy like ? and t.is_deleted = false order by o.hierarchy";
|
||||
|
||||
return this.jdbcTemplate.query(sql, tm, new Object[] { hierarchySearchString }); // NOSONAR
|
||||
}
|
||||
|
||||
+394
-451
@@ -1,451 +1,394 @@
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity;
|
||||
import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount;
|
||||
import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||
import org.apache.fineract.organisation.staff.domain.StaffRepository;
|
||||
import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
|
||||
import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator;
|
||||
import org.apache.fineract.organisation.teller.domain.Cashier;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierRepository;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierTransaction;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierTxnType;
|
||||
import org.apache.fineract.organisation.teller.domain.Teller;
|
||||
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.teller.exception.CashierExistForTellerException;
|
||||
import org.apache.fineract.organisation.teller.exception.CashierNotFoundException;
|
||||
import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformService {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final TellerRepositoryWrapper tellerRepositoryWrapper;
|
||||
private final OfficeRepositoryWrapper officeRepositoryWrapper;
|
||||
private final StaffRepository staffRepository;
|
||||
private final CashierRepository cashierRepository;
|
||||
private final CashierTransactionRepository cashierTxnRepository;
|
||||
private final JournalEntryRepository glJournalEntryRepository;
|
||||
private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper;
|
||||
private final CashierTransactionDataValidator cashierTransactionDataValidator;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult createTeller(JsonCommand command) {
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
|
||||
final Long officeId = command.longValueOfParameterNamed("officeId");
|
||||
|
||||
this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
|
||||
|
||||
// final Office parent =
|
||||
// validateUserPriviledgeOnOfficeAndRetrieve(currentUser, officeId);
|
||||
final Office tellerOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId);
|
||||
final Teller teller = Teller.fromJson(tellerOffice, command);
|
||||
|
||||
// pre save to generate id for use in office hierarchy
|
||||
this.tellerRepositoryWrapper.saveAndFlush(teller);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(teller.getId()) //
|
||||
.withOfficeId(teller.getOffice().getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult modifyTeller(Long tellerId, JsonCommand command) {
|
||||
try {
|
||||
|
||||
final Long officeId = command.longValueOfParameterNamed("officeId");
|
||||
final Office tellerOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId);
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
|
||||
this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
|
||||
|
||||
final Teller teller = validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);
|
||||
|
||||
final Map<String, Object> changes = teller.update(tellerOffice, command);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.tellerRepositoryWrapper.saveAndFlush(teller);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(teller.getId()) //
|
||||
.withOfficeId(teller.officeId()) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* used to restrict modifying operations to office that are either the users office or lower (child) in the office
|
||||
* hierarchy
|
||||
*/
|
||||
private Teller validateUserPriviledgeOnTellerAndRetrieve(final AppUser currentUser, final Long tellerId) {
|
||||
|
||||
final Long userOfficeId = currentUser.getOffice().getId();
|
||||
final Office userOffice = this.officeRepositoryWrapper.findOfficeHierarchy(userOfficeId);
|
||||
final Teller tellerToReturn = this.tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
|
||||
final Long tellerOfficeId = tellerToReturn.officeId();
|
||||
if (userOffice.doesNotHaveAnOfficeInHierarchyWithId(tellerOfficeId)) {
|
||||
throw new NoAuthorizationException("User does not have sufficient priviledges to act on the provided office.");
|
||||
}
|
||||
return tellerToReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult deleteTeller(Long tellerId) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
Teller teller = tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
|
||||
Set<Cashier> isTellerIdPresentInCashier = teller.getCashiers();
|
||||
|
||||
for (final Cashier tellerIdInCashier : isTellerIdPresentInCashier) {
|
||||
if (tellerIdInCashier.getTeller().getId().toString().equalsIgnoreCase(tellerId.toString())) {
|
||||
throw new CashierExistForTellerException(tellerId);
|
||||
}
|
||||
|
||||
}
|
||||
tellerRepositoryWrapper.delete(teller);
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withEntityId(teller.getId()) //
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Guaranteed to throw an exception no matter what the data integrity issue is.
|
||||
*/
|
||||
private void handleTellerDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dve) {
|
||||
if (realCause.getMessage().contains("m_tellers_name_unq")) {
|
||||
final String name = command.stringValueOfParameterNamed("name");
|
||||
throw new PlatformDataIntegrityException("error.msg.teller.duplicate.name", "Teller with name `" + name + "` already exists",
|
||||
"name", name);
|
||||
}
|
||||
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.teller.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult allocateCashierToTeller(final Long tellerId, JsonCommand command) {
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
Long hourStartTime;
|
||||
Long minStartTime;
|
||||
Long hourEndTime;
|
||||
Long minEndTime;
|
||||
String startTime = " ";
|
||||
String endTime = " ";
|
||||
final Teller teller = this.tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
|
||||
final Office tellerOffice = teller.getOffice();
|
||||
|
||||
final Long staffId = command.longValueOfParameterNamed("staffId");
|
||||
|
||||
this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());
|
||||
|
||||
final Staff staff = this.staffRepository.findById(staffId).orElseThrow(() -> new StaffNotFoundException(staffId));
|
||||
final Boolean isFullDay = command.booleanObjectValueOfParameterNamed("isFullDay");
|
||||
if (!isFullDay) {
|
||||
hourStartTime = command.longValueOfParameterNamed("hourStartTime");
|
||||
minStartTime = command.longValueOfParameterNamed("minStartTime");
|
||||
|
||||
if (minStartTime == 0) {
|
||||
startTime = hourStartTime.toString() + ":" + minStartTime.toString() + "0";
|
||||
} else {
|
||||
startTime = hourStartTime.toString() + ":" + minStartTime.toString();
|
||||
}
|
||||
|
||||
hourEndTime = command.longValueOfParameterNamed("hourEndTime");
|
||||
minEndTime = command.longValueOfParameterNamed("minEndTime");
|
||||
if (minEndTime == 0) {
|
||||
endTime = hourEndTime.toString() + ":" + minEndTime.toString() + "0";
|
||||
} else {
|
||||
endTime = hourEndTime.toString() + ":" + minEndTime.toString();
|
||||
}
|
||||
|
||||
}
|
||||
final Cashier cashier = Cashier.fromJson(tellerOffice, teller, staff, startTime, endTime, command);
|
||||
this.cashierTransactionDataValidator.validateCashierAllowedDateAndTime(cashier, teller);
|
||||
|
||||
this.cashierRepository.save(cashier);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(teller.getId()) //
|
||||
.withSubEntityId(cashier.getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult updateCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
|
||||
try {
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
|
||||
this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());
|
||||
|
||||
final Long staffId = command.longValueOfParameterNamed("staffId");
|
||||
final Staff staff = this.staffRepository.findById(staffId).orElseThrow(() -> new StaffNotFoundException(staffId));
|
||||
|
||||
final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);
|
||||
|
||||
cashier.setStaff(staff);
|
||||
|
||||
// TODO - check if staff office and teller office match
|
||||
|
||||
final Map<String, Object> changes = cashier.update(command);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.cashierRepository.saveAndFlush(cashier);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(cashier.getTeller().getId()) //
|
||||
.withSubEntityId(cashier.getId()) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Cashier validateUserPriviledgeOnCashierAndRetrieve(final AppUser currentUser, final Long tellerId, final Long cashierId) {
|
||||
|
||||
validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);
|
||||
|
||||
return this.cashierRepository.findById(cashierId).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult deleteCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
|
||||
try {
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);
|
||||
this.cashierRepository.delete(cashier);
|
||||
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withEntityId(cashierId) //
|
||||
.build();
|
||||
}
|
||||
|
||||
/*
|
||||
* @Override public CommandProcessingResult inwardCashToCashier (final Long cashierId, final CashierTransaction
|
||||
* cashierTxn) { CashierTxnType txnType = CashierTxnType.INWARD_CASH_TXN; // pre save to generate id for use in
|
||||
* office hierarchy this.cashierTxnRepository.save(cashierTxn); }
|
||||
*/
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult allocateCashToCashier(final Long cashierId, JsonCommand command) {
|
||||
return doTransactionForCashier(cashierId, CashierTxnType.ALLOCATE, command); // For
|
||||
// fund
|
||||
// allocation
|
||||
// to
|
||||
// cashier
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult settleCashFromCashier(final Long cashierId, JsonCommand command) {
|
||||
|
||||
this.cashierTransactionDataValidator.validateSettleCashAndCashOutTransactions(cashierId, command);
|
||||
|
||||
return doTransactionForCashier(cashierId, CashierTxnType.SETTLE, command); // For
|
||||
// fund
|
||||
// settlement
|
||||
// from
|
||||
// cashier
|
||||
}
|
||||
|
||||
private CommandProcessingResult doTransactionForCashier(final Long cashierId, final CashierTxnType txnType, JsonCommand command) {
|
||||
try {
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
|
||||
final Cashier cashier = this.cashierRepository.findById(cashierId).orElseThrow(() -> new CashierNotFoundException(cashierId));
|
||||
|
||||
this.fromApiJsonDeserializer.validateForCashTxnForCashier(command.json());
|
||||
|
||||
// TODO: can we please remove this whole block?!? this is 20 lines of dead code!!!
|
||||
final String entityType = command.stringValueOfParameterNamed("entityType");
|
||||
if (entityType != null) {
|
||||
if (entityType.equals("loan account")) {
|
||||
// TODO : Check if loan account exists
|
||||
// LoanAccount loan = null;
|
||||
// if (loan == null) { throw new
|
||||
// LoanAccountFoundException(entityId); }
|
||||
} else if (entityType.equals("savings account")) {
|
||||
// TODO : Check if loan account exists
|
||||
// SavingsAccount savingsaccount = null;
|
||||
// if (savingsaccount == null) { throw new
|
||||
// SavingsAccountNotFoundException(entityId); }
|
||||
|
||||
}
|
||||
if (entityType.equals("client")) {
|
||||
// TODO: Check if client exists
|
||||
// Client client = null;
|
||||
// if (client == null) { throw new
|
||||
// ClientNotFoundException(entityId); }
|
||||
} else {
|
||||
// TODO : Invalid type handling
|
||||
}
|
||||
}
|
||||
|
||||
final CashierTransaction cashierTxn = CashierTransaction.fromJson(cashier, command);
|
||||
cashierTxn.setTxnType(txnType.getId());
|
||||
|
||||
this.cashierTxnRepository.save(cashierTxn);
|
||||
|
||||
// Pass the journal entries
|
||||
FinancialActivityAccount mainVaultFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper
|
||||
.findByFinancialActivityTypeWithNotFoundDetection(FinancialActivity.CASH_AT_MAINVAULT.getValue());
|
||||
FinancialActivityAccount tellerCashFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper
|
||||
.findByFinancialActivityTypeWithNotFoundDetection(FinancialActivity.CASH_AT_TELLER.getValue());
|
||||
GLAccount creditAccount = null;
|
||||
GLAccount debitAccount = null;
|
||||
if (txnType.equals(CashierTxnType.ALLOCATE)) {
|
||||
debitAccount = tellerCashFinancialActivityAccount.getGlAccount();
|
||||
creditAccount = mainVaultFinancialActivityAccount.getGlAccount();
|
||||
} else if (txnType.equals(CashierTxnType.SETTLE)) {
|
||||
debitAccount = mainVaultFinancialActivityAccount.getGlAccount();
|
||||
creditAccount = tellerCashFinancialActivityAccount.getGlAccount();
|
||||
}
|
||||
|
||||
final Office cashierOffice = cashier.getTeller().getOffice();
|
||||
|
||||
final Long time = System.currentTimeMillis();
|
||||
final String uniqueVal = String.valueOf(time) + currentUser.getId() + cashierOffice.getId();
|
||||
final String transactionId = Long.toHexString(Long.parseLong(uniqueVal));
|
||||
|
||||
final JournalEntry debitJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment
|
||||
// detail
|
||||
debitAccount, cashierTxn.getCurrencyCode(),
|
||||
|
||||
transactionId, false, // manual entry
|
||||
cashierTxn.getTxnDate(), JournalEntryType.DEBIT, cashierTxn.getTxnAmount(), cashierTxn.getTxnNote(), // Description
|
||||
null, null, null, // entity Type, entityId, reference number
|
||||
null, null, null, null); // Loan
|
||||
// and
|
||||
// Savings
|
||||
// Txn
|
||||
|
||||
final JournalEntry creditJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment
|
||||
// detail
|
||||
creditAccount, cashierTxn.getCurrencyCode(),
|
||||
|
||||
transactionId, false, // manual entry
|
||||
cashierTxn.getTxnDate(), JournalEntryType.CREDIT, cashierTxn.getTxnAmount(), cashierTxn.getTxnNote(), // Description
|
||||
null, null, null, // entity Type, entityId, reference number
|
||||
null, null, null, null); // Loan
|
||||
// and
|
||||
// Savings
|
||||
// Txn
|
||||
|
||||
this.glJournalEntryRepository.saveAndFlush(debitJournalEntry);
|
||||
this.glJournalEntryRepository.saveAndFlush(creditJournalEntry);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(cashier.getId()) //
|
||||
.withSubEntityId(cashierTxn.getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.service;
|
||||
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||
import org.apache.fineract.organisation.staff.domain.StaffRepository;
|
||||
import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
|
||||
import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator;
|
||||
import org.apache.fineract.organisation.teller.domain.Cashier;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierRepository;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierTransaction;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierTxnType;
|
||||
import org.apache.fineract.organisation.teller.domain.Teller;
|
||||
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.teller.exception.CashierExistForTellerException;
|
||||
import org.apache.fineract.organisation.teller.exception.CashierNotFoundException;
|
||||
import org.apache.fineract.organisation.teller.exception.TellerAlreadyExistsException;
|
||||
import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformService {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final TellerRepositoryWrapper tellerRepositoryWrapper;
|
||||
private final OfficeRepositoryWrapper officeRepositoryWrapper;
|
||||
private final StaffRepository staffRepository;
|
||||
private final CashierRepository cashierRepository;
|
||||
private final CashierTransactionRepository cashierTxnRepository;
|
||||
private final CashierTransactionDataValidator cashierTransactionDataValidator;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult createTeller(JsonCommand command) {
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
|
||||
if (this.tellerRepositoryWrapper.existsAny()) {
|
||||
throw new TellerAlreadyExistsException();
|
||||
}
|
||||
|
||||
final Long officeId = command.longValueOfParameterNamed("officeId");
|
||||
|
||||
this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
|
||||
|
||||
// final Office parent =
|
||||
// validateUserPriviledgeOnOfficeAndRetrieve(currentUser, officeId);
|
||||
final Office tellerOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId);
|
||||
final Teller teller = Teller.fromJson(tellerOffice, command);
|
||||
|
||||
// pre save to generate id for use in office hierarchy
|
||||
this.tellerRepositoryWrapper.saveAndFlush(teller);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(teller.getId()) //
|
||||
.withOfficeId(teller.getOffice().getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult modifyTeller(Long tellerId, JsonCommand command) {
|
||||
try {
|
||||
|
||||
final Long officeId = command.longValueOfParameterNamed("officeId");
|
||||
final Office tellerOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId);
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
|
||||
this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
|
||||
|
||||
final Teller teller = validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);
|
||||
|
||||
final Map<String, Object> changes = teller.update(tellerOffice, command);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.tellerRepositoryWrapper.saveAndFlush(teller);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(teller.getId()) //
|
||||
.withOfficeId(teller.officeId()) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* used to restrict modifying operations to office that are either the users office or lower (child) in the office
|
||||
* hierarchy
|
||||
*/
|
||||
private Teller validateUserPriviledgeOnTellerAndRetrieve(final AppUser currentUser, final Long tellerId) {
|
||||
|
||||
final Long userOfficeId = currentUser.getOffice().getId();
|
||||
final Office userOffice = this.officeRepositoryWrapper.findOfficeHierarchy(userOfficeId);
|
||||
final Teller tellerToReturn = this.tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
|
||||
final Long tellerOfficeId = tellerToReturn.officeId();
|
||||
if (userOffice.doesNotHaveAnOfficeInHierarchyWithId(tellerOfficeId)) {
|
||||
throw new NoAuthorizationException("User does not have sufficient priviledges to act on the provided office.");
|
||||
}
|
||||
return tellerToReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult deleteTeller(Long tellerId) {
|
||||
Teller teller = tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
|
||||
teller.delete();
|
||||
tellerRepositoryWrapper.saveAndFlush(teller);
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withEntityId(teller.getId()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
/*
|
||||
* Guaranteed to throw an exception no matter what the data integrity issue is.
|
||||
*/
|
||||
private void handleTellerDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dve) {
|
||||
if (realCause.getMessage().contains("m_tellers_name_unq")) {
|
||||
final String name = command.stringValueOfParameterNamed("name");
|
||||
throw new PlatformDataIntegrityException("error.msg.teller.duplicate.name", "Teller with name `" + name + "` already exists",
|
||||
"name", name);
|
||||
}
|
||||
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.teller.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult allocateCashierToTeller(final Long tellerId, JsonCommand command) {
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
Long hourStartTime;
|
||||
Long minStartTime;
|
||||
Long hourEndTime;
|
||||
Long minEndTime;
|
||||
String startTime = " ";
|
||||
String endTime = " ";
|
||||
final Teller teller = this.tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
|
||||
final Office tellerOffice = teller.getOffice();
|
||||
|
||||
final Long staffId = command.longValueOfParameterNamed("staffId");
|
||||
|
||||
this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());
|
||||
|
||||
final Staff staff = this.staffRepository.findById(staffId).orElseThrow(() -> new StaffNotFoundException(staffId));
|
||||
final Boolean isFullDay = command.booleanObjectValueOfParameterNamed("isFullDay");
|
||||
if (!isFullDay) {
|
||||
hourStartTime = command.longValueOfParameterNamed("hourStartTime");
|
||||
minStartTime = command.longValueOfParameterNamed("minStartTime");
|
||||
|
||||
if (minStartTime == 0) {
|
||||
startTime = hourStartTime.toString() + ":" + minStartTime.toString() + "0";
|
||||
} else {
|
||||
startTime = hourStartTime.toString() + ":" + minStartTime.toString();
|
||||
}
|
||||
|
||||
hourEndTime = command.longValueOfParameterNamed("hourEndTime");
|
||||
minEndTime = command.longValueOfParameterNamed("minEndTime");
|
||||
if (minEndTime == 0) {
|
||||
endTime = hourEndTime.toString() + ":" + minEndTime.toString() + "0";
|
||||
} else {
|
||||
endTime = hourEndTime.toString() + ":" + minEndTime.toString();
|
||||
}
|
||||
|
||||
}
|
||||
final Cashier cashier = Cashier.fromJson(tellerOffice, teller, staff, startTime, endTime, command);
|
||||
this.cashierTransactionDataValidator.validateCashierAllowedDateAndTime(cashier, teller);
|
||||
|
||||
this.cashierRepository.save(cashier);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(teller.getId()) //
|
||||
.withSubEntityId(cashier.getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult updateCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
|
||||
try {
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
|
||||
this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());
|
||||
|
||||
final Long staffId = command.longValueOfParameterNamed("staffId");
|
||||
final Staff staff = this.staffRepository.findById(staffId).orElseThrow(() -> new StaffNotFoundException(staffId));
|
||||
|
||||
final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);
|
||||
|
||||
cashier.setStaff(staff);
|
||||
|
||||
// TODO - check if staff office and teller office match
|
||||
|
||||
final Map<String, Object> changes = cashier.update(command);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.cashierRepository.saveAndFlush(cashier);
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(cashier.getTeller().getId()) //
|
||||
.withSubEntityId(cashier.getId()) //
|
||||
.with(changes) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Cashier validateUserPriviledgeOnCashierAndRetrieve(final AppUser currentUser, final Long tellerId, final Long cashierId) {
|
||||
|
||||
validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);
|
||||
|
||||
return this.cashierRepository.findById(cashierId).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult deleteCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
|
||||
try {
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);
|
||||
this.cashierRepository.delete(cashier);
|
||||
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withEntityId(cashierId) //
|
||||
.build();
|
||||
}
|
||||
|
||||
/*
|
||||
* @Override public CommandProcessingResult inwardCashToCashier (final Long cashierId, final CashierTransaction
|
||||
* cashierTxn) { CashierTxnType txnType = CashierTxnType.INWARD_CASH_TXN; // pre save to generate id for use in
|
||||
* office hierarchy this.cashierTxnRepository.save(cashierTxn); }
|
||||
*/
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult allocateCashToCashier(final Long cashierId, JsonCommand command) {
|
||||
return doTransactionForCashier(cashierId, CashierTxnType.ALLOCATE, command); // For
|
||||
// fund
|
||||
// allocation
|
||||
// to
|
||||
// cashier
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult settleCashFromCashier(final Long cashierId, JsonCommand command) {
|
||||
|
||||
this.cashierTransactionDataValidator.validateSettleCashAndCashOutTransactions(cashierId, command);
|
||||
|
||||
return doTransactionForCashier(cashierId, CashierTxnType.SETTLE, command); // For
|
||||
// fund
|
||||
// settlement
|
||||
// from
|
||||
// cashier
|
||||
}
|
||||
|
||||
private CommandProcessingResult doTransactionForCashier(final Long cashierId, final CashierTxnType txnType, JsonCommand command) {
|
||||
try {
|
||||
final AppUser currentUser = this.context.authenticatedUser();
|
||||
|
||||
final Cashier cashier = this.cashierRepository.findById(cashierId).orElseThrow(() -> new CashierNotFoundException(cashierId));
|
||||
|
||||
this.fromApiJsonDeserializer.validateForCashTxnForCashier(command.json());
|
||||
|
||||
// TODO: can we please remove this whole block?!? this is 20 lines of dead code!!!
|
||||
final String entityType = command.stringValueOfParameterNamed("entityType");
|
||||
if (entityType != null) {
|
||||
if (entityType.equals("loan account")) {
|
||||
// TODO : Check if loan account exists
|
||||
// LoanAccount loan = null;
|
||||
// if (loan == null) { throw new
|
||||
// LoanAccountFoundException(entityId); }
|
||||
} else if (entityType.equals("savings account")) {
|
||||
// TODO : Check if loan account exists
|
||||
// SavingsAccount savingsaccount = null;
|
||||
// if (savingsaccount == null) { throw new
|
||||
// SavingsAccountNotFoundException(entityId); }
|
||||
|
||||
}
|
||||
if (entityType.equals("client")) {
|
||||
// TODO: Check if client exists
|
||||
// Client client = null;
|
||||
// if (client == null) { throw new
|
||||
// ClientNotFoundException(entityId); }
|
||||
} else {
|
||||
// TODO : Invalid type handling
|
||||
}
|
||||
}
|
||||
|
||||
final CashierTransaction cashierTxn = CashierTransaction.fromJson(cashier, command);
|
||||
cashierTxn.setTxnType(txnType.getId());
|
||||
|
||||
this.cashierTxnRepository.save(cashierTxn);
|
||||
|
||||
// GL entries skipped for vault-to-cashier (ALLOCATE) and cashier-to-vault (SETTLE)
|
||||
// transfers. Cash is internal bank physical movement — denomination tracking is
|
||||
// handled operationally via m_cash_transfer / m_cash_settlement tables.
|
||||
// GL impact happens only on customer deposit/withdrawal transactions.
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(cashier.getId()) //
|
||||
.withSubEntityId(cashierTxn.getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleTellerDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+67
-71
@@ -1,71 +1,67 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.organisation.teller.starter;
|
||||
|
||||
import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
|
||||
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
|
||||
import org.apache.fineract.organisation.staff.domain.StaffRepository;
|
||||
import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
|
||||
import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierRepository;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository;
|
||||
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformService;
|
||||
import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformServiceImpl;
|
||||
import org.apache.fineract.organisation.teller.service.TellerWritePlatformService;
|
||||
import org.apache.fineract.organisation.teller.service.TellerWritePlatformServiceJpaImpl;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class OrganisationTellerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TellerManagementReadPlatformService.class)
|
||||
public TellerManagementReadPlatformService tellerManagementReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
PlatformSecurityContext context, OfficeReadPlatformService officeReadPlatformService,
|
||||
StaffReadPlatformService staffReadPlatformService, CurrencyReadPlatformService currencyReadPlatformService,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper, SqlValidator sqlValidator) {
|
||||
return new TellerManagementReadPlatformServiceImpl(jdbcTemplate, context, officeReadPlatformService, staffReadPlatformService,
|
||||
currencyReadPlatformService, sqlGenerator, paginationHelper, sqlValidator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TellerWritePlatformService.class)
|
||||
public TellerWritePlatformService tellerWritePlatformService(PlatformSecurityContext context,
|
||||
TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer, TellerRepositoryWrapper tellerRepositoryWrapper,
|
||||
OfficeRepositoryWrapper officeRepositoryWrapper, StaffRepository staffRepository, CashierRepository cashierRepository,
|
||||
CashierTransactionRepository cashierTxnRepository, JournalEntryRepository glJournalEntryRepository,
|
||||
FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper,
|
||||
CashierTransactionDataValidator cashierTransactionDataValidator) {
|
||||
return new TellerWritePlatformServiceJpaImpl(context, fromApiJsonDeserializer, tellerRepositoryWrapper, officeRepositoryWrapper,
|
||||
staffRepository, cashierRepository, cashierTxnRepository, glJournalEntryRepository,
|
||||
financialActivityAccountRepositoryWrapper, cashierTransactionDataValidator);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.starter;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
|
||||
import org.apache.fineract.organisation.staff.domain.StaffRepository;
|
||||
import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
|
||||
import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierRepository;
|
||||
import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository;
|
||||
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
|
||||
import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformService;
|
||||
import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformServiceImpl;
|
||||
import org.apache.fineract.organisation.teller.service.TellerWritePlatformService;
|
||||
import org.apache.fineract.organisation.teller.service.TellerWritePlatformServiceJpaImpl;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class OrganisationTellerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TellerManagementReadPlatformService.class)
|
||||
public TellerManagementReadPlatformService tellerManagementReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
PlatformSecurityContext context, OfficeReadPlatformService officeReadPlatformService,
|
||||
StaffReadPlatformService staffReadPlatformService, CurrencyReadPlatformService currencyReadPlatformService,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper, SqlValidator sqlValidator) {
|
||||
return new TellerManagementReadPlatformServiceImpl(jdbcTemplate, context, officeReadPlatformService, staffReadPlatformService,
|
||||
currencyReadPlatformService, sqlGenerator, paginationHelper, sqlValidator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TellerWritePlatformService.class)
|
||||
public TellerWritePlatformService tellerWritePlatformService(PlatformSecurityContext context,
|
||||
TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer, TellerRepositoryWrapper tellerRepositoryWrapper,
|
||||
OfficeRepositoryWrapper officeRepositoryWrapper, StaffRepository staffRepository, CashierRepository cashierRepository,
|
||||
CashierTransactionRepository cashierTxnRepository,
|
||||
CashierTransactionDataValidator cashierTransactionDataValidator) {
|
||||
return new TellerWritePlatformServiceJpaImpl(context, fromApiJsonDeserializer, tellerRepositoryWrapper, officeRepositoryWrapper,
|
||||
staffRepository, cashierRepository, cashierTxnRepository, cashierTransactionDataValidator);
|
||||
}
|
||||
}
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.account.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import com.google.gson.JsonElement;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/accounts/{accountId}/terminate")
|
||||
@Component
|
||||
@Tag(name = "Account Terminate", description = "Unified terminate endpoint for Savings, Current, FD and RD accounts.")
|
||||
@RequiredArgsConstructor
|
||||
public class AccountTerminateApiResource {
|
||||
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final DefaultToApiJsonSerializer<CommandProcessingResult> toApiJsonSerializer;
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Terminate an account", description = "Terminates a Savings, Current, Fixed Deposit or Recurring Deposit account. Pass accountType in the request body: SAVINGS | CURRENT | FD | RD.")
|
||||
@ApiResponse(responseCode = "200", description = "OK")
|
||||
public String terminate(@PathParam("accountId") final Long accountId,
|
||||
final String apiRequestBodyAsJson) {
|
||||
|
||||
final String jsonBody = StringUtils.isBlank(apiRequestBodyAsJson) ? "{}" : apiRequestBodyAsJson;
|
||||
|
||||
final JsonElement root = this.fromJsonHelper.parse(jsonBody);
|
||||
final String accountType = this.fromJsonHelper.extractStringNamed("accountType", root);
|
||||
String entityName = "SAVINGSACCOUNT";
|
||||
if ("CURRENT".equalsIgnoreCase(accountType)) {
|
||||
entityName = "CURRENTACCOUNT";
|
||||
} else if ("FD".equalsIgnoreCase(accountType)) {
|
||||
entityName = "FIXEDDEPOSITACCOUNT";
|
||||
} else if ("RD".equalsIgnoreCase(accountType)) {
|
||||
entityName = "RECURRINGDEPOSITACCOUNT";
|
||||
}
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||
.withJson(jsonBody)
|
||||
.terminateAccount(accountId)
|
||||
.withEntityName(entityName)
|
||||
.build();
|
||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
return toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
+220
-220
@@ -1,220 +1,220 @@
|
||||
/**
|
||||
* 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.account.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.BeanParam;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.batch.command.CommandHandlerRegistry;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.infrastructure.core.service.SearchParameters;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransferData;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
||||
import org.apache.fineract.portfolio.account.data.request.StandingInstructionCreationRequest;
|
||||
import org.apache.fineract.portfolio.account.data.request.StandingInstructionSearchParam;
|
||||
import org.apache.fineract.portfolio.account.data.request.StandingInstructionUpdatesRequest;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/standinginstructions")
|
||||
@Component
|
||||
@Tag(name = "Standing Instructions", description = "Standing instructions (or standing orders) refer to instructions a bank account holder (\"the payer\") gives to his or her bank to pay a set amount at regular intervals to another's (\"the payee's\") account.\n"
|
||||
+ "\n" + "Note: At present only savings account to savings account and savings account to Loan account transfers are permitted.")
|
||||
@RequiredArgsConstructor
|
||||
public class StandingInstructionApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final DefaultToApiJsonSerializer<StandingInstructionData> toApiJsonSerializer;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
|
||||
private final SqlValidator sqlValidator;
|
||||
|
||||
private static final CommandHandlerRegistry<String, Long, String, CommandWrapper> COMMAND_HANDLER_REGISTRY = new CommandHandlerRegistry<>(
|
||||
Map.of(CommandParameterUtil.UPDATE_COMMAND_VALUE,
|
||||
(id, json) -> new CommandWrapperBuilder().updateStandingInstruction(id).withJson(json).build(),
|
||||
CommandParameterUtil.DELETE_COMMAND_VALUE,
|
||||
(id, json) -> new CommandWrapperBuilder().deleteStandingInstruction(id).withJson(json).build()));
|
||||
|
||||
@GET
|
||||
@Path("template")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve Standing Instruction Template", description = "This is a convenience resource. "
|
||||
+ "It can be useful when building maintenance user interface screens for client applications. "
|
||||
+ "The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed Value Lists\n"
|
||||
+ "Example Requests:\n" + "\n" + "standinginstructions/template?fromAccountType=2&fromOfficeId=1\n" + "\n"
|
||||
+ "standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1\n" + "\n"
|
||||
+ "standinginstructions/template?fromClientId=1&fromAccountType=2&fromAccountId=1&transferType=1")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsTemplateResponse.class)))
|
||||
public StandingInstructionData template(@BeanParam StandingInstructionSearchParam instructionParam) {
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
return standingInstructionReadPlatformService.retrieveTemplate(instructionParam.getFromOfficeId(),
|
||||
instructionParam.getFromClientId(), instructionParam.getFromAccountId(), instructionParam.getFromAccountType(),
|
||||
instructionParam.getToOfficeId(), instructionParam.getToClientId(), instructionParam.getToAccountId(),
|
||||
instructionParam.getToAccountType(), instructionParam.getTransferType());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create new Standing Instruction", description = "Ability to create new instruction for transfer of monetary funds from one account to another")
|
||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = StandingInstructionCreationRequest.class)))
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PostStandingInstructionsResponse.class)))
|
||||
public CommandProcessingResult create(@Parameter(hidden = true) StandingInstructionCreationRequest creationRequest) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createStandingInstruction()
|
||||
.withJson(toApiJsonSerializer.serialize(creationRequest)).build();
|
||||
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{standingInstructionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update Standing Instruction | Delete Standing Instruction", description = "Ability to modify existing instruction for transfer of monetary funds from one account to another.\n"
|
||||
+ "\n" + "PUT https://DomainName/api/v1/standinginstructions/1?command=update\n" + "\n\n"
|
||||
+ "Ability to modify existing instruction for transfer of monetary funds from one account to another.\n" + "\n"
|
||||
+ "PUT https://DomainName/api/v1/standinginstructions/1?command=delete")
|
||||
@RequestBody(content = @Content(schema = @Schema(implementation = StandingInstructionUpdatesRequest.class)))
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PutStandingInstructionsStandingInstructionIdResponse.class)))
|
||||
public CommandProcessingResult update(
|
||||
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
|
||||
@Parameter(hidden = true) StandingInstructionUpdatesRequest updatesRequest,
|
||||
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
|
||||
|
||||
final String serializedUpdatesRequest = toApiJsonSerializer.serialize(updatesRequest);
|
||||
final CommandWrapper commandRequest = COMMAND_HANDLER_REGISTRY.execute(commandParam, standingInstructionId,
|
||||
serializedUpdatesRequest, new UnrecognizedQueryParamException("command", commandParam));
|
||||
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "List Standing Instructions", description = "Example Requests:\n" + "\n" + "standinginstructions")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsResponse.class)))
|
||||
public Page<StandingInstructionData> retrieveAll(
|
||||
@QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||
@QueryParam("transferType") @Parameter(description = "transferType") final Integer transferType,
|
||||
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName,
|
||||
@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId,
|
||||
@QueryParam("fromAccountId") @Parameter(description = "fromAccountId") final Long fromAccount,
|
||||
@QueryParam("fromAccountType") @Parameter(description = "fromAccountType") final Integer fromAccountType) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
sqlValidator.validate(orderBy);
|
||||
sqlValidator.validate(sortOrder);
|
||||
sqlValidator.validate(externalId);
|
||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
|
||||
final LocalDate startDateRange = null;
|
||||
final LocalDate endDateRange = null;
|
||||
StandingInstructionDTO standingInstructionDTO = new StandingInstructionDTO(searchParameters, transferType, clientName, clientId,
|
||||
fromAccount, fromAccountType, startDateRange, endDateRange);
|
||||
|
||||
return standingInstructionReadPlatformService.retrieveAll(standingInstructionDTO);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{standingInstructionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve Standing Instruction", description = "Example Requests :\n" + "\n" + "standinginstructions/1")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsStandingInstructionIdResponse.class)))
|
||||
public StandingInstructionData retrieveOne(
|
||||
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
|
||||
@Context final UriInfo uriInfo, @QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
sqlValidator.validate(orderBy);
|
||||
sqlValidator.validate(sortOrder);
|
||||
StandingInstructionData standingInstructionData = standingInstructionReadPlatformService.retrieveOne(standingInstructionId);
|
||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||
if (!associationParameters.isEmpty()) {
|
||||
if (associationParameters.contains("all")) {
|
||||
associationParameters.addAll(Arrays.asList("transactions", "template"));
|
||||
}
|
||||
if (associationParameters.contains("transactions")) {
|
||||
Page<AccountTransferData> transfers = accountTransfersReadPlatformService
|
||||
.retrieveByStandingInstruction(standingInstructionId, searchParameters);
|
||||
standingInstructionData = StandingInstructionData.withTransferData(standingInstructionData, transfers);
|
||||
}
|
||||
if (associationParameters.contains("template")) {
|
||||
final StandingInstructionData templateData = standingInstructionReadPlatformService.retrieveTemplate(
|
||||
standingInstructionData.getFromClient().getOfficeId(), standingInstructionData.getFromClient().getId(),
|
||||
standingInstructionData.getFromAccount().getId(), standingInstructionData.getFromAccountType().getValue(),
|
||||
standingInstructionData.getToClient().getOfficeId(), standingInstructionData.getToClient().getId(),
|
||||
standingInstructionData.getToAccount().getId(), standingInstructionData.getToAccountType().getValue(),
|
||||
standingInstructionData.getTransferType().getValue());
|
||||
standingInstructionData = StandingInstructionData.withTemplateData(standingInstructionData, templateData);
|
||||
}
|
||||
}
|
||||
|
||||
return standingInstructionData;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.BeanParam;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.batch.command.CommandHandlerRegistry;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.infrastructure.core.service.SearchParameters;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransferData;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDTO;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
||||
import org.apache.fineract.portfolio.account.data.request.StandingInstructionCreationRequest;
|
||||
import org.apache.fineract.portfolio.account.data.request.StandingInstructionSearchParam;
|
||||
import org.apache.fineract.portfolio.account.data.request.StandingInstructionUpdatesRequest;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/standinginstructions")
|
||||
@Component
|
||||
@Tag(name = "Standing Instructions", description = "Standing instructions (or standing orders) refer to instructions a bank account holder (\"the payer\") gives to his or her bank to pay a set amount at regular intervals to another's (\"the payee's\") account.\n"
|
||||
+ "\n" + "Note: At present only savings account to savings account and savings account to Loan account transfers are permitted.")
|
||||
@RequiredArgsConstructor
|
||||
public class StandingInstructionApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final DefaultToApiJsonSerializer<StandingInstructionData> toApiJsonSerializer;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
|
||||
private final SqlValidator sqlValidator;
|
||||
|
||||
private static final CommandHandlerRegistry<String, Long, String, CommandWrapper> COMMAND_HANDLER_REGISTRY = new CommandHandlerRegistry<>(
|
||||
Map.of(CommandParameterUtil.UPDATE_COMMAND_VALUE,
|
||||
(id, json) -> new CommandWrapperBuilder().updateStandingInstruction(id).withJson(json).build(),
|
||||
CommandParameterUtil.DELETE_COMMAND_VALUE,
|
||||
(id, json) -> new CommandWrapperBuilder().deleteStandingInstruction(id).withJson(json).build()));
|
||||
|
||||
@GET
|
||||
@Path("template")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve Standing Instruction Template", description = "This is a convenience resource. "
|
||||
+ "It can be useful when building maintenance user interface screens for client applications. "
|
||||
+ "The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed Value Lists\n"
|
||||
+ "Example Requests:\n" + "\n" + "standinginstructions/template?fromAccountType=2&fromOfficeId=1\n" + "\n"
|
||||
+ "standinginstructions/template?fromAccountType=2&fromOfficeId=1&fromClientId=1&transferType=1\n" + "\n"
|
||||
+ "standinginstructions/template?fromClientId=1&fromAccountType=2&fromAccountId=1&transferType=1")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsTemplateResponse.class)))
|
||||
public StandingInstructionData template(@BeanParam StandingInstructionSearchParam instructionParam) {
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
return standingInstructionReadPlatformService.retrieveTemplate(instructionParam.getFromOfficeId(),
|
||||
instructionParam.getFromClientId(), instructionParam.getFromAccountId(), instructionParam.getFromAccountType(),
|
||||
instructionParam.getToOfficeId(), instructionParam.getToClientId(), instructionParam.getToAccountId(),
|
||||
instructionParam.getToAccountType(), instructionParam.getTransferType());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create new Standing Instruction", description = "Ability to create new instruction for transfer of monetary funds from one account to another")
|
||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = StandingInstructionCreationRequest.class)))
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PostStandingInstructionsResponse.class)))
|
||||
public CommandProcessingResult create(@Parameter(hidden = true) StandingInstructionCreationRequest creationRequest) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createStandingInstruction()
|
||||
.withJson(toApiJsonSerializer.serialize(creationRequest)).build();
|
||||
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{standingInstructionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update Standing Instruction | Delete Standing Instruction", description = "Ability to modify existing instruction for transfer of monetary funds from one account to another.\n"
|
||||
+ "\n" + "PUT https://DomainName/api/v1/standinginstructions/1?command=update\n" + "\n\n"
|
||||
+ "Ability to modify existing instruction for transfer of monetary funds from one account to another.\n" + "\n"
|
||||
+ "PUT https://DomainName/api/v1/standinginstructions/1?command=delete")
|
||||
@RequestBody(content = @Content(schema = @Schema(implementation = StandingInstructionUpdatesRequest.class)))
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.PutStandingInstructionsStandingInstructionIdResponse.class)))
|
||||
public CommandProcessingResult update(
|
||||
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
|
||||
@Parameter(hidden = true) StandingInstructionUpdatesRequest updatesRequest,
|
||||
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
|
||||
|
||||
final String serializedUpdatesRequest = toApiJsonSerializer.serialize(updatesRequest);
|
||||
final CommandWrapper commandRequest = COMMAND_HANDLER_REGISTRY.execute(commandParam, standingInstructionId,
|
||||
serializedUpdatesRequest, new UnrecognizedQueryParamException("command", commandParam));
|
||||
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "List Standing Instructions", description = "Example Requests:\n" + "\n" + "standinginstructions")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsResponse.class)))
|
||||
public Page<StandingInstructionData> retrieveAll(
|
||||
@QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||
@QueryParam("transferType") @Parameter(description = "transferType") final Integer transferType,
|
||||
@QueryParam("clientName") @Parameter(description = "clientName") final String clientName,
|
||||
@QueryParam("clientId") @Parameter(description = "clientId") final Long clientId,
|
||||
@QueryParam("fromAccountId") @Parameter(description = "fromAccountId") final Long fromAccount,
|
||||
@QueryParam("fromAccountType") @Parameter(description = "fromAccountType") final Integer fromAccountType) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
sqlValidator.validate(orderBy);
|
||||
sqlValidator.validate(sortOrder);
|
||||
sqlValidator.validate(externalId);
|
||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
|
||||
final LocalDate startDateRange = null;
|
||||
final LocalDate endDateRange = null;
|
||||
StandingInstructionDTO standingInstructionDTO = new StandingInstructionDTO(searchParameters, transferType, clientName, clientId,
|
||||
fromAccount, fromAccountType, startDateRange, endDateRange);
|
||||
|
||||
return standingInstructionReadPlatformService.retrieveAll(standingInstructionDTO);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{standingInstructionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve Standing Instruction", description = "Example Requests :\n" + "\n" + "standinginstructions/1")
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StandingInstructionApiResourceSwagger.GetStandingInstructionsStandingInstructionIdResponse.class)))
|
||||
public StandingInstructionData retrieveOne(
|
||||
@PathParam("standingInstructionId") @Parameter(description = "standingInstructionId") final Long standingInstructionId,
|
||||
@Context final UriInfo uriInfo, @QueryParam("externalId") @Parameter(description = "externalId") final String externalId,
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
sqlValidator.validate(orderBy);
|
||||
sqlValidator.validate(sortOrder);
|
||||
StandingInstructionData standingInstructionData = standingInstructionReadPlatformService.retrieveOne(standingInstructionId);
|
||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||
if (!associationParameters.isEmpty()) {
|
||||
if (associationParameters.contains("all")) {
|
||||
associationParameters.addAll(Arrays.asList("transactions", "template"));
|
||||
}
|
||||
if (associationParameters.contains("transactions")) {
|
||||
Page<AccountTransferData> transfers = accountTransfersReadPlatformService
|
||||
.retrieveByStandingInstruction(standingInstructionId, searchParameters);
|
||||
standingInstructionData = StandingInstructionData.withTransferData(standingInstructionData, transfers);
|
||||
}
|
||||
if (associationParameters.contains("template")) {
|
||||
final StandingInstructionData templateData = standingInstructionReadPlatformService.retrieveTemplate(
|
||||
standingInstructionData.getFromClient().getOfficeId(), standingInstructionData.getFromClient().getId(),
|
||||
standingInstructionData.getFromAccount().getId(), standingInstructionData.fromAccountTypeEnum().getValue(),
|
||||
standingInstructionData.getToClient().getOfficeId(), standingInstructionData.getToClient().getId(),
|
||||
standingInstructionData.getToAccount().getId(), standingInstructionData.toAccountTypeEnum().getValue(),
|
||||
standingInstructionData.transferTypeEnum().getValue());
|
||||
standingInstructionData = StandingInstructionData.withTemplateData(standingInstructionData, templateData);
|
||||
}
|
||||
}
|
||||
|
||||
return standingInstructionData;
|
||||
}
|
||||
}
|
||||
|
||||
+79
-79
@@ -1,79 +1,79 @@
|
||||
/**
|
||||
* 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.account.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class AccountTransfersDetailDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public AccountTransfersDetailDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validate(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromOfficeIdParamName).value(fromOfficeId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromClientIdParamName).value(fromClientId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long fromAccountId = this.fromApiJsonHelper.extractLongNamed(fromAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromAccountIdParamName).value(fromAccountId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
||||
baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType).notNull()
|
||||
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toClientIdParamName).value(toClientId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long toAccountId = this.fromApiJsonHelper.extractLongNamed(toAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toAccountIdParamName).value(toAccountId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||
baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType).notNull().isOneOfTheseValues(Integer.valueOf(1),
|
||||
Integer.valueOf(2));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.account.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class AccountTransfersDetailDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public AccountTransfersDetailDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validate(final JsonCommand command, final DataValidatorBuilder baseDataValidator) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromOfficeIdParamName).value(fromOfficeId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromClientIdParamName).value(fromClientId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long fromAccountId = this.fromApiJsonHelper.extractLongNamed(fromAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(fromAccountIdParamName).value(fromAccountId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
||||
baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType).notNull()
|
||||
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3));
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toClientIdParamName).value(toClientId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Long toAccountId = this.fromApiJsonHelper.extractLongNamed(toAccountIdParamName, element);
|
||||
baseDataValidator.reset().parameter(toAccountIdParamName).value(toAccountId).notNull().integerGreaterThanZero();
|
||||
|
||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||
baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType).notNull().isOneOfTheseValues(Integer.valueOf(1),
|
||||
Integer.valueOf(2), Integer.valueOf(3));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+359
-329
@@ -1,329 +1,359 @@
|
||||
/**
|
||||
* 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.account.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.organisation.office.data.OfficeData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.client.data.ClientData;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
|
||||
/**
|
||||
* Immutable data object representing a savings account.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class StandingInstructionData {
|
||||
|
||||
@Getter
|
||||
private final Long id;
|
||||
@Getter
|
||||
private final Long accountDetailId;
|
||||
@Getter
|
||||
private final String name;
|
||||
private final OfficeData fromOffice;
|
||||
@Getter
|
||||
private final ClientData fromClient;
|
||||
private final EnumOptionData fromAccountType;
|
||||
@Getter
|
||||
private final PortfolioAccountData fromAccount;
|
||||
private final OfficeData toOffice;
|
||||
@Getter
|
||||
private final ClientData toClient;
|
||||
private final EnumOptionData toAccountType;
|
||||
@Getter
|
||||
private final PortfolioAccountData toAccount;
|
||||
private final EnumOptionData transferType;
|
||||
private final EnumOptionData priority;
|
||||
private final EnumOptionData instructionType;
|
||||
@Getter
|
||||
private final EnumOptionData status;
|
||||
@Getter
|
||||
private final BigDecimal amount;
|
||||
@Getter
|
||||
private final LocalDate validFrom;
|
||||
private final LocalDate validTill;
|
||||
private final EnumOptionData recurrenceType;
|
||||
private final EnumOptionData recurrenceFrequency;
|
||||
@Getter
|
||||
private final Integer recurrenceInterval;
|
||||
private final MonthDay recurrenceOnMonthDay;
|
||||
private final Page<AccountTransferData> transactions;
|
||||
|
||||
private final Collection<OfficeData> fromOfficeOptions;
|
||||
private final Collection<ClientData> fromClientOptions;
|
||||
private final Collection<EnumOptionData> fromAccountTypeOptions;
|
||||
private final Collection<PortfolioAccountData> fromAccountOptions;
|
||||
private final Collection<OfficeData> toOfficeOptions;
|
||||
private final Collection<ClientData> toClientOptions;
|
||||
private final Collection<EnumOptionData> toAccountTypeOptions;
|
||||
private final Collection<PortfolioAccountData> toAccountOptions;
|
||||
private final Collection<EnumOptionData> transferTypeOptions;
|
||||
private final Collection<EnumOptionData> statusOptions;
|
||||
private final Collection<EnumOptionData> instructionTypeOptions;
|
||||
private final Collection<EnumOptionData> priorityOptions;
|
||||
private final Collection<EnumOptionData> recurrenceTypeOptions;
|
||||
private final Collection<EnumOptionData> recurrenceFrequencyOptions;
|
||||
|
||||
public static StandingInstructionData template(final Collection<OfficeData> fromOfficeOptions,
|
||||
final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
|
||||
final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
|
||||
final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
|
||||
final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
|
||||
final Long id = null;
|
||||
final Long accountDetailId = null;
|
||||
final String name = null;
|
||||
final OfficeData fromOffice = null;
|
||||
final OfficeData toOffice = null;
|
||||
final ClientData fromClient = null;
|
||||
final ClientData toClient = null;
|
||||
final EnumOptionData fromAccountType = null;
|
||||
final PortfolioAccountData fromAccount = null;
|
||||
final EnumOptionData toAccountType = null;
|
||||
final PortfolioAccountData toAccount = null;
|
||||
final EnumOptionData transferType = null;
|
||||
final EnumOptionData priority = null;
|
||||
final EnumOptionData instructionType = null;
|
||||
final EnumOptionData status = null;
|
||||
final BigDecimal amount = null;
|
||||
final LocalDate validFrom = null;
|
||||
final LocalDate validTill = null;
|
||||
final EnumOptionData recurrenceType = null;
|
||||
final EnumOptionData recurrenceFrequency = null;
|
||||
final Integer recurrenceInterval = null;
|
||||
final MonthDay recurrenceOnMonthDay = null;
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData instance(final Long id, final Long accountDetailId, final String name,
|
||||
final OfficeData fromOffice, final OfficeData toOffice, final ClientData fromClient, final ClientData toClient,
|
||||
final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final EnumOptionData toAccountType,
|
||||
final PortfolioAccountData toAccount, final EnumOptionData transferType, final EnumOptionData priority,
|
||||
final EnumOptionData instructionType, final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency,
|
||||
final Integer recurrenceInterval, final MonthDay recurrenceOnMonthDay) {
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
final Collection<OfficeData> fromOfficeOptions = null;
|
||||
final Collection<ClientData> fromClientOptions = null;
|
||||
final Collection<EnumOptionData> fromAccountTypeOptions = null;
|
||||
final Collection<PortfolioAccountData> fromAccountOptions = null;
|
||||
final Collection<OfficeData> toOfficeOptions = null;
|
||||
final Collection<ClientData> toClientOptions = null;
|
||||
final Collection<EnumOptionData> toAccountTypeOptions = null;
|
||||
final Collection<PortfolioAccountData> toAccountOptions = null;
|
||||
final Collection<EnumOptionData> transferTypeOptions = null;
|
||||
final Collection<EnumOptionData> statusOptions = null;
|
||||
final Collection<EnumOptionData> instructionTypeOptions = null;
|
||||
final Collection<EnumOptionData> priorityOptions = null;
|
||||
final Collection<EnumOptionData> recurrenceTypeOptions = null;
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData withTemplateData(StandingInstructionData instructionData, StandingInstructionData templateData) {
|
||||
return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
|
||||
instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
|
||||
instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
|
||||
instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
|
||||
instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
|
||||
instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay,
|
||||
instructionData.transactions, templateData.fromOfficeOptions, templateData.fromClientOptions,
|
||||
templateData.fromAccountTypeOptions, templateData.fromAccountOptions, templateData.toOfficeOptions,
|
||||
templateData.toClientOptions, templateData.toAccountTypeOptions, templateData.toAccountOptions,
|
||||
templateData.transferTypeOptions, templateData.statusOptions, templateData.instructionTypeOptions,
|
||||
templateData.priorityOptions, templateData.recurrenceTypeOptions, templateData.recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
private StandingInstructionData(final Long id, final Long accountDetailId, final String name, final OfficeData fromOffice,
|
||||
final ClientData fromClient, final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount,
|
||||
final OfficeData toOffice, final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
|
||||
final EnumOptionData transferType, final EnumOptionData priority, final EnumOptionData instructionType,
|
||||
final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom, LocalDate validTill,
|
||||
final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final MonthDay recurrenceOnMonthDay, final Page<AccountTransferData> transactions,
|
||||
final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
|
||||
final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
|
||||
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
|
||||
final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions,
|
||||
final Collection<EnumOptionData> transferTypeOptions, final Collection<EnumOptionData> statusOptions,
|
||||
final Collection<EnumOptionData> instructionTypeOptions, final Collection<EnumOptionData> priorityOptions,
|
||||
final Collection<EnumOptionData> recurrenceTypeOptions, final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
this.id = id;
|
||||
this.accountDetailId = accountDetailId;
|
||||
this.name = name;
|
||||
this.fromOffice = fromOffice;
|
||||
this.fromClient = fromClient;
|
||||
this.fromAccountType = fromAccountType;
|
||||
this.fromAccount = fromAccount;
|
||||
this.toOffice = toOffice;
|
||||
this.toClient = toClient;
|
||||
this.toAccountType = toAccountType;
|
||||
this.toAccount = toAccount;
|
||||
this.transferType = transferType;
|
||||
|
||||
this.priority = priority;
|
||||
this.instructionType = instructionType;
|
||||
this.status = status;
|
||||
this.amount = amount;
|
||||
this.validFrom = validFrom;
|
||||
this.validTill = validTill;
|
||||
this.recurrenceType = recurrenceType;
|
||||
this.recurrenceFrequency = recurrenceFrequency;
|
||||
this.recurrenceInterval = recurrenceInterval;
|
||||
this.recurrenceOnMonthDay = recurrenceOnMonthDay;
|
||||
|
||||
this.fromOfficeOptions = fromOfficeOptions;
|
||||
this.fromClientOptions = fromClientOptions;
|
||||
this.fromAccountTypeOptions = fromAccountTypeOptions;
|
||||
this.fromAccountOptions = fromAccountOptions;
|
||||
this.toOfficeOptions = toOfficeOptions;
|
||||
this.toClientOptions = toClientOptions;
|
||||
this.toAccountTypeOptions = toAccountTypeOptions;
|
||||
this.toAccountOptions = toAccountOptions;
|
||||
this.transferTypeOptions = transferTypeOptions;
|
||||
this.statusOptions = statusOptions;
|
||||
this.instructionTypeOptions = instructionTypeOptions;
|
||||
this.priorityOptions = priorityOptions;
|
||||
this.recurrenceTypeOptions = recurrenceTypeOptions;
|
||||
this.recurrenceFrequencyOptions = recurrenceFrequencyOptions;
|
||||
this.transactions = transactions;
|
||||
}
|
||||
|
||||
public static StandingInstructionData template(OfficeData fromOffice, ClientData fromClient, EnumOptionData fromAccountType,
|
||||
PortfolioAccountData fromAccount, LocalDate transferDate, OfficeData toOffice, ClientData toClient,
|
||||
EnumOptionData toAccountType, PortfolioAccountData toAccount, final Collection<OfficeData> fromOfficeOptions,
|
||||
final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
|
||||
final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
|
||||
final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
|
||||
final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
final Long id = null;
|
||||
final Long accountDetailId = null;
|
||||
final String name = null;
|
||||
final EnumOptionData transferType = null;
|
||||
final EnumOptionData priority = null;
|
||||
final EnumOptionData instructionType = null;
|
||||
final EnumOptionData status = null;
|
||||
final BigDecimal amount = null;
|
||||
final LocalDate validFrom = null;
|
||||
final LocalDate validTill = null;
|
||||
final EnumOptionData recurrenceType = null;
|
||||
final EnumOptionData recurrenceFrequency = null;
|
||||
final Integer recurrenceInterval = null;
|
||||
final MonthDay recurrenceOnMonthDay = null;
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData withTransferData(StandingInstructionData instructionData,
|
||||
final Page<AccountTransferData> transactions) {
|
||||
return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
|
||||
instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
|
||||
instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
|
||||
instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
|
||||
instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
|
||||
instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay, transactions,
|
||||
instructionData.fromOfficeOptions, instructionData.fromClientOptions, instructionData.fromAccountTypeOptions,
|
||||
instructionData.fromAccountOptions, instructionData.toOfficeOptions, instructionData.toClientOptions,
|
||||
instructionData.toAccountTypeOptions, instructionData.toAccountOptions, instructionData.transferTypeOptions,
|
||||
instructionData.statusOptions, instructionData.instructionTypeOptions, instructionData.priorityOptions,
|
||||
instructionData.recurrenceTypeOptions, instructionData.recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public StandingInstructionType getInstructionType() {
|
||||
return Optional.ofNullable(this.instructionType).map(e -> StandingInstructionType.fromInt(e.getId().intValue())).orElse(null);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferRecurrenceType getRecurrenceType() {
|
||||
return Optional.ofNullable(this.recurrenceType).map(e -> AccountTransferRecurrenceType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PeriodFrequencyType getRecurrenceFrequency() {
|
||||
return Optional.ofNullable(this.recurrenceFrequency).map(e -> PeriodFrequencyType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PortfolioAccountType getFromAccountType() {
|
||||
return Optional.ofNullable(this.fromAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PortfolioAccountType getToAccountType() {
|
||||
return Optional.ofNullable(this.toAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public AccountTransferType getTransferType() {
|
||||
return Optional.ofNullable(this.transferType).map(e -> AccountTransferType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public Integer getRecurrenceOnDay() {
|
||||
return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.DAY_OF_MONTH)).orElse(0);
|
||||
}
|
||||
|
||||
public Integer getRecurrenceOnMonth() {
|
||||
return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.MONTH_OF_YEAR)).orElse(0);
|
||||
}
|
||||
|
||||
public Integer toTransferType() {
|
||||
Integer transferType = null;
|
||||
AccountTransferType accountTransferType = getTransferType();
|
||||
if (accountTransferType.isChargePayment()) {
|
||||
transferType = LoanTransactionType.CHARGE_PAYMENT.getValue();
|
||||
} else if (accountTransferType.isLoanRepayment()) {
|
||||
transferType = LoanTransactionType.REPAYMENT.getValue();
|
||||
}
|
||||
return transferType;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.account.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.organisation.office.data.OfficeData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.client.data.ClientData;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
|
||||
/**
|
||||
* Immutable data object representing a savings account.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class StandingInstructionData {
|
||||
|
||||
@Getter
|
||||
private final Long id;
|
||||
@Getter
|
||||
private final Long accountDetailId;
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final OfficeData fromOffice;
|
||||
@Getter
|
||||
private final ClientData fromClient;
|
||||
@Getter
|
||||
private final EnumOptionData fromAccountType;
|
||||
@Getter
|
||||
private final PortfolioAccountData fromAccount;
|
||||
@Getter
|
||||
private final OfficeData toOffice;
|
||||
@Getter
|
||||
private final ClientData toClient;
|
||||
@Getter
|
||||
private final EnumOptionData toAccountType;
|
||||
@Getter
|
||||
private final PortfolioAccountData toAccount;
|
||||
@Getter
|
||||
private final EnumOptionData transferType;
|
||||
@Getter
|
||||
private final EnumOptionData priority;
|
||||
@Getter
|
||||
private final EnumOptionData instructionType;
|
||||
@Getter
|
||||
private final EnumOptionData status;
|
||||
@Getter
|
||||
private final BigDecimal amount;
|
||||
@Getter
|
||||
private final LocalDate validFrom;
|
||||
@Getter
|
||||
private final LocalDate validTill;
|
||||
@Getter
|
||||
private final EnumOptionData recurrenceType;
|
||||
@Getter
|
||||
private final EnumOptionData recurrenceFrequency;
|
||||
@Getter
|
||||
private final Integer recurrenceInterval;
|
||||
@Getter
|
||||
private final MonthDay recurrenceOnMonthDay;
|
||||
@Getter
|
||||
private final Page<AccountTransferData> transactions;
|
||||
|
||||
@Getter
|
||||
private final Collection<OfficeData> fromOfficeOptions;
|
||||
@Getter
|
||||
private final Collection<ClientData> fromClientOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> fromAccountTypeOptions;
|
||||
@Getter
|
||||
private final Collection<PortfolioAccountData> fromAccountOptions;
|
||||
@Getter
|
||||
private final Collection<OfficeData> toOfficeOptions;
|
||||
@Getter
|
||||
private final Collection<ClientData> toClientOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> toAccountTypeOptions;
|
||||
@Getter
|
||||
private final Collection<PortfolioAccountData> toAccountOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> transferTypeOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> statusOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> instructionTypeOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> priorityOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> recurrenceTypeOptions;
|
||||
@Getter
|
||||
private final Collection<EnumOptionData> recurrenceFrequencyOptions;
|
||||
|
||||
public static StandingInstructionData template(final Collection<OfficeData> fromOfficeOptions,
|
||||
final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
|
||||
final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
|
||||
final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
|
||||
final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
|
||||
final Long id = null;
|
||||
final Long accountDetailId = null;
|
||||
final String name = null;
|
||||
final OfficeData fromOffice = null;
|
||||
final OfficeData toOffice = null;
|
||||
final ClientData fromClient = null;
|
||||
final ClientData toClient = null;
|
||||
final EnumOptionData fromAccountType = null;
|
||||
final PortfolioAccountData fromAccount = null;
|
||||
final EnumOptionData toAccountType = null;
|
||||
final PortfolioAccountData toAccount = null;
|
||||
final EnumOptionData transferType = null;
|
||||
final EnumOptionData priority = null;
|
||||
final EnumOptionData instructionType = null;
|
||||
final EnumOptionData status = null;
|
||||
final BigDecimal amount = null;
|
||||
final LocalDate validFrom = null;
|
||||
final LocalDate validTill = null;
|
||||
final EnumOptionData recurrenceType = null;
|
||||
final EnumOptionData recurrenceFrequency = null;
|
||||
final Integer recurrenceInterval = null;
|
||||
final MonthDay recurrenceOnMonthDay = null;
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData instance(final Long id, final Long accountDetailId, final String name,
|
||||
final OfficeData fromOffice, final OfficeData toOffice, final ClientData fromClient, final ClientData toClient,
|
||||
final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount, final EnumOptionData toAccountType,
|
||||
final PortfolioAccountData toAccount, final EnumOptionData transferType, final EnumOptionData priority,
|
||||
final EnumOptionData instructionType, final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency,
|
||||
final Integer recurrenceInterval, final MonthDay recurrenceOnMonthDay) {
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
final Collection<OfficeData> fromOfficeOptions = null;
|
||||
final Collection<ClientData> fromClientOptions = null;
|
||||
final Collection<EnumOptionData> fromAccountTypeOptions = null;
|
||||
final Collection<PortfolioAccountData> fromAccountOptions = null;
|
||||
final Collection<OfficeData> toOfficeOptions = null;
|
||||
final Collection<ClientData> toClientOptions = null;
|
||||
final Collection<EnumOptionData> toAccountTypeOptions = null;
|
||||
final Collection<PortfolioAccountData> toAccountOptions = null;
|
||||
final Collection<EnumOptionData> transferTypeOptions = null;
|
||||
final Collection<EnumOptionData> statusOptions = null;
|
||||
final Collection<EnumOptionData> instructionTypeOptions = null;
|
||||
final Collection<EnumOptionData> priorityOptions = null;
|
||||
final Collection<EnumOptionData> recurrenceTypeOptions = null;
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData withTemplateData(StandingInstructionData instructionData, StandingInstructionData templateData) {
|
||||
return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
|
||||
instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
|
||||
instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
|
||||
instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
|
||||
instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
|
||||
instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay,
|
||||
instructionData.transactions, templateData.fromOfficeOptions, templateData.fromClientOptions,
|
||||
templateData.fromAccountTypeOptions, templateData.fromAccountOptions, templateData.toOfficeOptions,
|
||||
templateData.toClientOptions, templateData.toAccountTypeOptions, templateData.toAccountOptions,
|
||||
templateData.transferTypeOptions, templateData.statusOptions, templateData.instructionTypeOptions,
|
||||
templateData.priorityOptions, templateData.recurrenceTypeOptions, templateData.recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
private StandingInstructionData(final Long id, final Long accountDetailId, final String name, final OfficeData fromOffice,
|
||||
final ClientData fromClient, final EnumOptionData fromAccountType, final PortfolioAccountData fromAccount,
|
||||
final OfficeData toOffice, final ClientData toClient, final EnumOptionData toAccountType, final PortfolioAccountData toAccount,
|
||||
final EnumOptionData transferType, final EnumOptionData priority, final EnumOptionData instructionType,
|
||||
final EnumOptionData status, final BigDecimal amount, final LocalDate validFrom, LocalDate validTill,
|
||||
final EnumOptionData recurrenceType, final EnumOptionData recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final MonthDay recurrenceOnMonthDay, final Page<AccountTransferData> transactions,
|
||||
final Collection<OfficeData> fromOfficeOptions, final Collection<ClientData> fromClientOptions,
|
||||
final Collection<EnumOptionData> fromAccountTypeOptions, final Collection<PortfolioAccountData> fromAccountOptions,
|
||||
final Collection<OfficeData> toOfficeOptions, final Collection<ClientData> toClientOptions,
|
||||
final Collection<EnumOptionData> toAccountTypeOptions, final Collection<PortfolioAccountData> toAccountOptions,
|
||||
final Collection<EnumOptionData> transferTypeOptions, final Collection<EnumOptionData> statusOptions,
|
||||
final Collection<EnumOptionData> instructionTypeOptions, final Collection<EnumOptionData> priorityOptions,
|
||||
final Collection<EnumOptionData> recurrenceTypeOptions, final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
this.id = id;
|
||||
this.accountDetailId = accountDetailId;
|
||||
this.name = name;
|
||||
this.fromOffice = fromOffice;
|
||||
this.fromClient = fromClient;
|
||||
this.fromAccountType = fromAccountType;
|
||||
this.fromAccount = fromAccount;
|
||||
this.toOffice = toOffice;
|
||||
this.toClient = toClient;
|
||||
this.toAccountType = toAccountType;
|
||||
this.toAccount = toAccount;
|
||||
this.transferType = transferType;
|
||||
|
||||
this.priority = priority;
|
||||
this.instructionType = instructionType;
|
||||
this.status = status;
|
||||
this.amount = amount;
|
||||
this.validFrom = validFrom;
|
||||
this.validTill = validTill;
|
||||
this.recurrenceType = recurrenceType;
|
||||
this.recurrenceFrequency = recurrenceFrequency;
|
||||
this.recurrenceInterval = recurrenceInterval;
|
||||
this.recurrenceOnMonthDay = recurrenceOnMonthDay;
|
||||
|
||||
this.fromOfficeOptions = fromOfficeOptions;
|
||||
this.fromClientOptions = fromClientOptions;
|
||||
this.fromAccountTypeOptions = fromAccountTypeOptions;
|
||||
this.fromAccountOptions = fromAccountOptions;
|
||||
this.toOfficeOptions = toOfficeOptions;
|
||||
this.toClientOptions = toClientOptions;
|
||||
this.toAccountTypeOptions = toAccountTypeOptions;
|
||||
this.toAccountOptions = toAccountOptions;
|
||||
this.transferTypeOptions = transferTypeOptions;
|
||||
this.statusOptions = statusOptions;
|
||||
this.instructionTypeOptions = instructionTypeOptions;
|
||||
this.priorityOptions = priorityOptions;
|
||||
this.recurrenceTypeOptions = recurrenceTypeOptions;
|
||||
this.recurrenceFrequencyOptions = recurrenceFrequencyOptions;
|
||||
this.transactions = transactions;
|
||||
}
|
||||
|
||||
public static StandingInstructionData template(OfficeData fromOffice, ClientData fromClient, EnumOptionData fromAccountType,
|
||||
PortfolioAccountData fromAccount, LocalDate transferDate, OfficeData toOffice, ClientData toClient,
|
||||
EnumOptionData toAccountType, PortfolioAccountData toAccount, final Collection<OfficeData> fromOfficeOptions,
|
||||
final Collection<ClientData> fromClientOptions, final Collection<EnumOptionData> fromAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> fromAccountOptions, final Collection<OfficeData> toOfficeOptions,
|
||||
final Collection<ClientData> toClientOptions, final Collection<EnumOptionData> toAccountTypeOptions,
|
||||
final Collection<PortfolioAccountData> toAccountOptions, final Collection<EnumOptionData> transferTypeOptions,
|
||||
final Collection<EnumOptionData> statusOptions, final Collection<EnumOptionData> instructionTypeOptions,
|
||||
final Collection<EnumOptionData> priorityOptions, final Collection<EnumOptionData> recurrenceTypeOptions,
|
||||
final Collection<EnumOptionData> recurrenceFrequencyOptions) {
|
||||
final Long id = null;
|
||||
final Long accountDetailId = null;
|
||||
final String name = null;
|
||||
final EnumOptionData transferType = null;
|
||||
final EnumOptionData priority = null;
|
||||
final EnumOptionData instructionType = null;
|
||||
final EnumOptionData status = null;
|
||||
final BigDecimal amount = null;
|
||||
final LocalDate validFrom = null;
|
||||
final LocalDate validTill = null;
|
||||
final EnumOptionData recurrenceType = null;
|
||||
final EnumOptionData recurrenceFrequency = null;
|
||||
final Integer recurrenceInterval = null;
|
||||
final MonthDay recurrenceOnMonthDay = null;
|
||||
final Page<AccountTransferData> transactions = null;
|
||||
|
||||
return new StandingInstructionData(id, accountDetailId, name, fromOffice, fromClient, fromAccountType, fromAccount, toOffice,
|
||||
toClient, toAccountType, toAccount, transferType, priority, instructionType, status, amount, validFrom, validTill,
|
||||
recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay, transactions, fromOfficeOptions,
|
||||
fromClientOptions, fromAccountTypeOptions, fromAccountOptions, toOfficeOptions, toClientOptions, toAccountTypeOptions,
|
||||
toAccountOptions, transferTypeOptions, statusOptions, instructionTypeOptions, priorityOptions, recurrenceTypeOptions,
|
||||
recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
public static StandingInstructionData withTransferData(StandingInstructionData instructionData,
|
||||
final Page<AccountTransferData> transactions) {
|
||||
return new StandingInstructionData(instructionData.id, instructionData.accountDetailId, instructionData.name,
|
||||
instructionData.fromOffice, instructionData.fromClient, instructionData.fromAccountType, instructionData.fromAccount,
|
||||
instructionData.toOffice, instructionData.toClient, instructionData.toAccountType, instructionData.toAccount,
|
||||
instructionData.transferType, instructionData.priority, instructionData.instructionType, instructionData.status,
|
||||
instructionData.amount, instructionData.validFrom, instructionData.validTill, instructionData.recurrenceType,
|
||||
instructionData.recurrenceFrequency, instructionData.recurrenceInterval, instructionData.recurrenceOnMonthDay, transactions,
|
||||
instructionData.fromOfficeOptions, instructionData.fromClientOptions, instructionData.fromAccountTypeOptions,
|
||||
instructionData.fromAccountOptions, instructionData.toOfficeOptions, instructionData.toClientOptions,
|
||||
instructionData.toAccountTypeOptions, instructionData.toAccountOptions, instructionData.transferTypeOptions,
|
||||
instructionData.statusOptions, instructionData.instructionTypeOptions, instructionData.priorityOptions,
|
||||
instructionData.recurrenceTypeOptions, instructionData.recurrenceFrequencyOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain enum for instruction type — used internally by business logic.
|
||||
* NOTE: renamed from getInstructionType() to avoid overriding Lombok @Getter (which must
|
||||
* return EnumOptionData so Jackson serializes it as an object, not a plain string).
|
||||
*/
|
||||
public StandingInstructionType instructionTypeEnum() {
|
||||
return Optional.ofNullable(this.instructionType).map(e -> StandingInstructionType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public AccountTransferRecurrenceType recurrenceTypeEnum() {
|
||||
return Optional.ofNullable(this.recurrenceType).map(e -> AccountTransferRecurrenceType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PeriodFrequencyType recurrenceFrequencyEnum() {
|
||||
return Optional.ofNullable(this.recurrenceFrequency).map(e -> PeriodFrequencyType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PortfolioAccountType fromAccountTypeEnum() {
|
||||
return Optional.ofNullable(this.fromAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public PortfolioAccountType toAccountTypeEnum() {
|
||||
return Optional.ofNullable(this.toAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public AccountTransferType transferTypeEnum() {
|
||||
return Optional.ofNullable(this.transferType).map(e -> AccountTransferType.fromInt(e.getId().intValue())).orElse(null);
|
||||
}
|
||||
|
||||
public Integer getRecurrenceOnDay() {
|
||||
return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.DAY_OF_MONTH)).orElse(0);
|
||||
}
|
||||
|
||||
public Integer getRecurrenceOnMonth() {
|
||||
return Optional.ofNullable(this.recurrenceOnMonthDay).map(monthDay -> monthDay.get(ChronoField.MONTH_OF_YEAR)).orElse(0);
|
||||
}
|
||||
|
||||
public Integer toTransferType() {
|
||||
Integer transferType = null;
|
||||
AccountTransferType accountTransferType = transferTypeEnum();
|
||||
if (accountTransferType != null && accountTransferType.isChargePayment()) {
|
||||
transferType = LoanTransactionType.CHARGE_PAYMENT.getValue();
|
||||
} else if (accountTransferType != null && accountTransferType.isLoanRepayment()) {
|
||||
transferType = LoanTransactionType.REPAYMENT.getValue();
|
||||
}
|
||||
return transferType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+296
-294
@@ -1,294 +1,296 @@
|
||||
/**
|
||||
* 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.account.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.portfolio.account.AccountDetailConstants;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class StandingInstructionDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator;
|
||||
private static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.localeParamName,
|
||||
AccountDetailConstants.dateFormatParamName, AccountDetailConstants.fromOfficeIdParamName,
|
||||
AccountDetailConstants.fromClientIdParamName, AccountDetailConstants.fromAccountTypeParamName,
|
||||
AccountDetailConstants.fromAccountIdParamName, AccountDetailConstants.toOfficeIdParamName,
|
||||
AccountDetailConstants.toClientIdParamName, AccountDetailConstants.toAccountTypeParamName,
|
||||
AccountDetailConstants.toAccountIdParamName, AccountDetailConstants.transferTypeParamName,
|
||||
StandingInstructionApiConstants.priorityParamName, StandingInstructionApiConstants.instructionTypeParamName,
|
||||
StandingInstructionApiConstants.statusParamName, StandingInstructionApiConstants.amountParamName,
|
||||
StandingInstructionApiConstants.validFromParamName, StandingInstructionApiConstants.validTillParamName,
|
||||
StandingInstructionApiConstants.recurrenceTypeParamName, StandingInstructionApiConstants.recurrenceFrequencyParamName,
|
||||
StandingInstructionApiConstants.recurrenceIntervalParamName, StandingInstructionApiConstants.recurrenceOnMonthDayParamName,
|
||||
StandingInstructionApiConstants.nameParamName, StandingInstructionApiConstants.monthDayFormatParamName));
|
||||
|
||||
private static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.localeParamName,
|
||||
AccountDetailConstants.dateFormatParamName, StandingInstructionApiConstants.priorityParamName,
|
||||
StandingInstructionApiConstants.instructionTypeParamName, StandingInstructionApiConstants.statusParamName,
|
||||
StandingInstructionApiConstants.amountParamName, StandingInstructionApiConstants.validFromParamName,
|
||||
StandingInstructionApiConstants.validTillParamName, StandingInstructionApiConstants.recurrenceTypeParamName,
|
||||
StandingInstructionApiConstants.recurrenceFrequencyParamName, StandingInstructionApiConstants.recurrenceIntervalParamName,
|
||||
StandingInstructionApiConstants.recurrenceOnMonthDayParamName, StandingInstructionApiConstants.monthDayFormatParamName));
|
||||
|
||||
@Autowired
|
||||
public StandingInstructionDataValidator(final FromJsonHelper fromApiJsonHelper,
|
||||
final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.accountTransfersDetailDataValidator = accountTransfersDetailDataValidator;
|
||||
}
|
||||
|
||||
public void validateForCreate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CREATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
this.accountTransfersDetailDataValidator.validate(command, baseDataValidator);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Integer status = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.statusParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.statusParamName).value(status).notNull().inMinMaxRange(1, 2);
|
||||
|
||||
final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validFromParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validFromParamName).value(validFrom).notNull();
|
||||
|
||||
final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validTillParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validTillParamName).value(validTill)
|
||||
.validateDateAfter(validFrom);
|
||||
|
||||
final BigDecimal transferAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(StandingInstructionApiConstants.amountParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).positiveAmount();
|
||||
|
||||
final Integer transferType = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).value(transferType).notNull().inMinMaxRange(1, 3);
|
||||
|
||||
final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.priorityParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.priorityParamName).value(priority).notNull().inMinMaxRange(1,
|
||||
4);
|
||||
|
||||
final Integer standingInstructionType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.instructionTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 2);
|
||||
|
||||
final Integer recurrenceType = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.recurrenceTypeParamName,
|
||||
element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 2);
|
||||
boolean isPeriodic = false;
|
||||
if (recurrenceType != null) {
|
||||
isPeriodic = AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence();
|
||||
}
|
||||
|
||||
final Integer recurrenceFrequency = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceFrequencyParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.inMinMaxRange(0, 3);
|
||||
|
||||
if (recurrenceFrequency != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(recurrenceFrequency);
|
||||
if (frequencyType.isMonthly() || frequencyType.isYearly()) {
|
||||
final MonthDay monthDay = this.fromApiJsonHelper
|
||||
.extractMonthDayNamed(StandingInstructionApiConstants.recurrenceOnMonthDayParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceOnMonthDayParamName).value(monthDay)
|
||||
.notNull();
|
||||
}
|
||||
}
|
||||
|
||||
final Integer recurrenceInterval = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceIntervalParamName, element, Locale.getDefault());
|
||||
if (isPeriodic) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.notNull();
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.notNull();
|
||||
}
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.integerGreaterThanZero();
|
||||
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(StandingInstructionApiConstants.nameParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
||||
|
||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||
if (toAccountType != null && PortfolioAccountType.fromInt(toAccountType).isSavingsAccount()) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 1);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 1);
|
||||
|
||||
}
|
||||
if (standingInstructionType != null && StandingInstructionType.fromInt(standingInstructionType).isFixedAmoutTransfer()) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).notNull();
|
||||
}
|
||||
|
||||
String errorCode = null;
|
||||
AccountTransferType accountTransferType = AccountTransferType.fromInt(transferType);
|
||||
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
||||
if (fromAccountType != null && toAccountType != null) {
|
||||
PortfolioAccountType fromPortfolioAccountType = PortfolioAccountType.fromInt(fromAccountType);
|
||||
PortfolioAccountType toPortfolioAccountType = PortfolioAccountType.fromInt(toAccountType);
|
||||
if (accountTransferType.isAccountTransfer()
|
||||
&& (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isLoanAccount())) {
|
||||
errorCode = "not.account.transfer";
|
||||
} else if (accountTransferType.isLoanRepayment()
|
||||
&& (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isSavingsAccount())) {
|
||||
errorCode = "not.loan.repayment";
|
||||
}
|
||||
if (errorCode != null) {
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForUpdate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, UPDATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.validFromParamName, element)) {
|
||||
final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validFromParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validFromParamName).value(validFrom).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.validTillParamName, element)) {
|
||||
final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validTillParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validTillParamName).value(validTill).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.amountParamName, element)) {
|
||||
final BigDecimal transferAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(StandingInstructionApiConstants.amountParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).positiveAmount();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.statusParamName, element)) {
|
||||
final Integer status = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.statusParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.statusParamName).value(status).notNull().inMinMaxRange(1,
|
||||
2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.priorityParamName, element)) {
|
||||
final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.priorityParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.priorityParamName).value(priority).notNull()
|
||||
.inMinMaxRange(1, 4);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.instructionTypeParamName, element)) {
|
||||
final Integer standingInstructionType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.instructionTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceTypeParamName, element)) {
|
||||
final Integer recurrenceType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceFrequencyParamName, element)) {
|
||||
final Integer recurrenceFrequency = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceFrequencyParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.inMinMaxRange(0, 3);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceIntervalParamName, element)) {
|
||||
final Integer recurrenceInterval = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceIntervalParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.nameParamName, element)) {
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(StandingInstructionApiConstants.nameParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.data;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.portfolio.account.AccountDetailConstants;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class StandingInstructionDataValidator {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator;
|
||||
private static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.localeParamName,
|
||||
AccountDetailConstants.dateFormatParamName, AccountDetailConstants.fromOfficeIdParamName,
|
||||
AccountDetailConstants.fromClientIdParamName, AccountDetailConstants.fromAccountTypeParamName,
|
||||
AccountDetailConstants.fromAccountIdParamName, AccountDetailConstants.toOfficeIdParamName,
|
||||
AccountDetailConstants.toClientIdParamName, AccountDetailConstants.toAccountTypeParamName,
|
||||
AccountDetailConstants.toAccountIdParamName, AccountDetailConstants.transferTypeParamName,
|
||||
StandingInstructionApiConstants.priorityParamName, StandingInstructionApiConstants.instructionTypeParamName,
|
||||
StandingInstructionApiConstants.statusParamName, StandingInstructionApiConstants.amountParamName,
|
||||
StandingInstructionApiConstants.validFromParamName, StandingInstructionApiConstants.validTillParamName,
|
||||
StandingInstructionApiConstants.recurrenceTypeParamName, StandingInstructionApiConstants.recurrenceFrequencyParamName,
|
||||
StandingInstructionApiConstants.recurrenceIntervalParamName, StandingInstructionApiConstants.recurrenceOnMonthDayParamName,
|
||||
StandingInstructionApiConstants.nameParamName, StandingInstructionApiConstants.monthDayFormatParamName));
|
||||
|
||||
private static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(AccountDetailConstants.localeParamName,
|
||||
AccountDetailConstants.dateFormatParamName, StandingInstructionApiConstants.priorityParamName,
|
||||
StandingInstructionApiConstants.instructionTypeParamName, StandingInstructionApiConstants.statusParamName,
|
||||
StandingInstructionApiConstants.amountParamName, StandingInstructionApiConstants.validFromParamName,
|
||||
StandingInstructionApiConstants.validTillParamName, StandingInstructionApiConstants.recurrenceTypeParamName,
|
||||
StandingInstructionApiConstants.recurrenceFrequencyParamName, StandingInstructionApiConstants.recurrenceIntervalParamName,
|
||||
StandingInstructionApiConstants.recurrenceOnMonthDayParamName, StandingInstructionApiConstants.monthDayFormatParamName));
|
||||
|
||||
@Autowired
|
||||
public StandingInstructionDataValidator(final FromJsonHelper fromApiJsonHelper,
|
||||
final AccountTransfersDetailDataValidator accountTransfersDetailDataValidator) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.accountTransfersDetailDataValidator = accountTransfersDetailDataValidator;
|
||||
}
|
||||
|
||||
public void validateForCreate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CREATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
this.accountTransfersDetailDataValidator.validate(command, baseDataValidator);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Integer status = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.statusParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.statusParamName).value(status).notNull().inMinMaxRange(1, 2);
|
||||
|
||||
final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validFromParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validFromParamName).value(validFrom).notNull();
|
||||
|
||||
final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validTillParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validTillParamName).value(validTill)
|
||||
.validateDateAfter(validFrom);
|
||||
|
||||
final BigDecimal transferAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(StandingInstructionApiConstants.amountParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).positiveAmount();
|
||||
|
||||
final Integer transferType = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).value(transferType).notNull().inMinMaxRange(1, 3);
|
||||
|
||||
final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.priorityParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.priorityParamName).value(priority).notNull().inMinMaxRange(1,
|
||||
4);
|
||||
|
||||
final Integer standingInstructionType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.instructionTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 2);
|
||||
|
||||
final Integer recurrenceType = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.recurrenceTypeParamName,
|
||||
element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 2);
|
||||
boolean isPeriodic = false;
|
||||
if (recurrenceType != null) {
|
||||
isPeriodic = AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence();
|
||||
}
|
||||
|
||||
final Integer recurrenceFrequency = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceFrequencyParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.inMinMaxRange(0, 3);
|
||||
|
||||
if (recurrenceFrequency != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(recurrenceFrequency);
|
||||
if (frequencyType.isMonthly() || frequencyType.isYearly()) {
|
||||
final MonthDay monthDay = this.fromApiJsonHelper
|
||||
.extractMonthDayNamed(StandingInstructionApiConstants.recurrenceOnMonthDayParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceOnMonthDayParamName).value(monthDay)
|
||||
.notNull();
|
||||
}
|
||||
}
|
||||
|
||||
final Integer recurrenceInterval = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceIntervalParamName, element, Locale.getDefault());
|
||||
if (isPeriodic) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.notNull();
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.notNull();
|
||||
}
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.integerGreaterThanZero();
|
||||
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(StandingInstructionApiConstants.nameParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
||||
|
||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||
if (toAccountType != null && (PortfolioAccountType.fromInt(toAccountType).isSavingsAccount()
|
||||
|| PortfolioAccountType.fromInt(toAccountType).isCurrentAccount())) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 1);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 1);
|
||||
|
||||
}
|
||||
if (standingInstructionType != null && StandingInstructionType.fromInt(standingInstructionType).isFixedAmoutTransfer()) {
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).notNull();
|
||||
}
|
||||
|
||||
String errorCode = null;
|
||||
AccountTransferType accountTransferType = AccountTransferType.fromInt(transferType);
|
||||
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
||||
if (fromAccountType != null && toAccountType != null) {
|
||||
PortfolioAccountType fromPortfolioAccountType = PortfolioAccountType.fromInt(fromAccountType);
|
||||
PortfolioAccountType toPortfolioAccountType = PortfolioAccountType.fromInt(toAccountType);
|
||||
if (accountTransferType.isAccountTransfer()
|
||||
&& (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isLoanAccount())) {
|
||||
errorCode = "not.account.transfer";
|
||||
} else if (accountTransferType.isLoanRepayment()
|
||||
&& (fromPortfolioAccountType.isLoanAccount()
|
||||
|| (toPortfolioAccountType.isSavingsAccount() || toPortfolioAccountType.isCurrentAccount()))) {
|
||||
errorCode = "not.loan.repayment";
|
||||
}
|
||||
if (errorCode != null) {
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForUpdate(final JsonCommand command) {
|
||||
final String json = command.json();
|
||||
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, UPDATE_REQUEST_DATA_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.validFromParamName, element)) {
|
||||
final LocalDate validFrom = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validFromParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validFromParamName).value(validFrom).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.validTillParamName, element)) {
|
||||
final LocalDate validTill = this.fromApiJsonHelper.extractLocalDateNamed(StandingInstructionApiConstants.validTillParamName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.validTillParamName).value(validTill).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.amountParamName, element)) {
|
||||
final BigDecimal transferAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(StandingInstructionApiConstants.amountParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.amountParamName).value(transferAmount).positiveAmount();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.statusParamName, element)) {
|
||||
final Integer status = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.statusParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.statusParamName).value(status).notNull().inMinMaxRange(1,
|
||||
2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.priorityParamName, element)) {
|
||||
final Integer priority = this.fromApiJsonHelper.extractIntegerNamed(StandingInstructionApiConstants.priorityParamName, element,
|
||||
Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.priorityParamName).value(priority).notNull()
|
||||
.inMinMaxRange(1, 4);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.instructionTypeParamName, element)) {
|
||||
final Integer standingInstructionType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.instructionTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||
.notNull().inMinMaxRange(1, 2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceTypeParamName, element)) {
|
||||
final Integer recurrenceType = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceTypeParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||
.inMinMaxRange(1, 2);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceFrequencyParamName, element)) {
|
||||
final Integer recurrenceFrequency = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceFrequencyParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceFrequencyParamName).value(recurrenceFrequency)
|
||||
.inMinMaxRange(0, 3);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.recurrenceIntervalParamName, element)) {
|
||||
final Integer recurrenceInterval = this.fromApiJsonHelper
|
||||
.extractIntegerNamed(StandingInstructionApiConstants.recurrenceIntervalParamName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceIntervalParamName).value(recurrenceInterval)
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(StandingInstructionApiConstants.nameParamName, element)) {
|
||||
final String name = this.fromApiJsonHelper.extractStringNamed(StandingInstructionApiConstants.nameParamName, element);
|
||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+314
-214
@@ -1,214 +1,314 @@
|
||||
/**
|
||||
* 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.account.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import java.util.Locale;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AccountTransferDetailAssembler {
|
||||
|
||||
private final ClientRepositoryWrapper clientRepository;
|
||||
private final OfficeRepositoryWrapper officeRepositoryWrapper;
|
||||
private final SavingsAccountAssembler savingsAccountAssembler;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanAssembler loanAccountAssembler;
|
||||
|
||||
@Autowired
|
||||
public AccountTransferDetailAssembler(final ClientRepositoryWrapper clientRepository,
|
||||
final OfficeRepositoryWrapper officeRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
|
||||
final FromJsonHelper fromApiJsonHelper, final LoanAssembler loanAccountAssembler) {
|
||||
this.clientRepository = clientRepository;
|
||||
this.officeRepositoryWrapper = officeRepositoryWrapper;
|
||||
this.savingsAccountAssembler = savingsAccountAssembler;
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.loanAccountAssembler = loanAccountAssembler;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId, false);
|
||||
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, backdatedTxnsAllowedTill);
|
||||
|
||||
return assembleSavingsToSavingsTransfer(command, fromSavingsAccount, toSavingsAccount);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId, backdatedTxnsAllowedTill);
|
||||
|
||||
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
|
||||
|
||||
return assembleSavingsToLoanTransfer(command, fromSavingsAccount, toLoanAccount);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill);
|
||||
|
||||
return assembleLoanToSavingsTransfer(command, fromLoanAccount, toSavingsAccount);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
|
||||
final SavingsAccount toSavingsAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toSavingsAccount, transfertype);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
|
||||
final Loan toLoanAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
|
||||
transfertype);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command, final Loan fromLoanAccount,
|
||||
final SavingsAccount toSavingsAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.loanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
|
||||
transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final SavingsAccount fromSavingsAccount, final Loan toLoanAccount,
|
||||
Integer transferType) {
|
||||
final Office fromOffice = fromSavingsAccount.office();
|
||||
final Client fromClient = fromSavingsAccount.getClient();
|
||||
final Office toOffice = toLoanAccount.getOffice();
|
||||
final Client toClient = toLoanAccount.client();
|
||||
|
||||
return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
|
||||
transferType);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final SavingsAccount fromSavingsAccount,
|
||||
final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
final Office fromOffice = fromSavingsAccount.office();
|
||||
final Client fromClient = fromSavingsAccount.getClient();
|
||||
final Office toOffice = toSavingsAccount.office();
|
||||
final Client toClient = toSavingsAccount.getClient();
|
||||
|
||||
return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toSavingsAccount, transferType);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final Loan fromLoanAccount, final SavingsAccount toSavingsAccount,
|
||||
Integer transferType) {
|
||||
final Office fromOffice = fromLoanAccount.getOffice();
|
||||
final Client fromClient = fromLoanAccount.client();
|
||||
final Office toOffice = toSavingsAccount.office();
|
||||
final Client toClient = toSavingsAccount.getClient();
|
||||
|
||||
return AccountTransferDetails.loanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
|
||||
transferType);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToLoanTransfer(Loan fromLoanAccount, Loan toLoanAccount, Integer transferType) {
|
||||
final Office fromOffice = fromLoanAccount.getOffice();
|
||||
final Client fromClient = fromLoanAccount.client();
|
||||
final Office toOffice = toLoanAccount.getOffice();
|
||||
final Client toClient = toLoanAccount.client();
|
||||
|
||||
return AccountTransferDetails.loanToLoanTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toLoanAccount,
|
||||
transferType);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import java.util.Locale;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AccountTransferDetailAssembler {
|
||||
|
||||
private final ClientRepositoryWrapper clientRepository;
|
||||
private final OfficeRepositoryWrapper officeRepositoryWrapper;
|
||||
private final SavingsAccountAssembler savingsAccountAssembler;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanAssembler loanAccountAssembler;
|
||||
private final CurrentAccountRepository currentAccountRepository;
|
||||
|
||||
@Autowired
|
||||
public AccountTransferDetailAssembler(final ClientRepositoryWrapper clientRepository,
|
||||
final OfficeRepositoryWrapper officeRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
|
||||
final FromJsonHelper fromApiJsonHelper, final LoanAssembler loanAccountAssembler,
|
||||
final CurrentAccountRepository currentAccountRepository) {
|
||||
this.clientRepository = clientRepository;
|
||||
this.officeRepositoryWrapper = officeRepositoryWrapper;
|
||||
this.savingsAccountAssembler = savingsAccountAssembler;
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.loanAccountAssembler = loanAccountAssembler;
|
||||
this.currentAccountRepository = currentAccountRepository;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId, false);
|
||||
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, backdatedTxnsAllowedTill);
|
||||
|
||||
return assembleSavingsToSavingsTransfer(command, fromSavingsAccount, toSavingsAccount);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId, backdatedTxnsAllowedTill);
|
||||
|
||||
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
|
||||
|
||||
return assembleSavingsToLoanTransfer(command, fromSavingsAccount, toLoanAccount);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
|
||||
|
||||
final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
|
||||
final boolean backdatedTxnsAllowedTill = false;
|
||||
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill);
|
||||
|
||||
return assembleLoanToSavingsTransfer(command, fromLoanAccount, toSavingsAccount);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
|
||||
final SavingsAccount toSavingsAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toSavingsAccount, transfertype);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command, final SavingsAccount fromSavingsAccount,
|
||||
final Loan toLoanAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
|
||||
transfertype);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command, final Loan fromLoanAccount,
|
||||
final SavingsAccount toSavingsAccount) {
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.loanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
|
||||
transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final SavingsAccount fromSavingsAccount, final Loan toLoanAccount,
|
||||
Integer transferType) {
|
||||
final Office fromOffice = fromSavingsAccount.office();
|
||||
final Client fromClient = fromSavingsAccount.getClient();
|
||||
final Office toOffice = toLoanAccount.getOffice();
|
||||
final Client toClient = toLoanAccount.client();
|
||||
|
||||
return AccountTransferDetails.savingsToLoanTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient, toLoanAccount,
|
||||
transferType);
|
||||
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final SavingsAccount fromSavingsAccount,
|
||||
final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
final Office fromOffice = fromSavingsAccount.office();
|
||||
final Client fromClient = fromSavingsAccount.getClient();
|
||||
final Office toOffice = toSavingsAccount.office();
|
||||
final Client toClient = toSavingsAccount.getClient();
|
||||
|
||||
return AccountTransferDetails.savingsToSavingsTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toSavingsAccount, transferType);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final Loan fromLoanAccount, final SavingsAccount toSavingsAccount,
|
||||
Integer transferType) {
|
||||
final Office fromOffice = fromLoanAccount.getOffice();
|
||||
final Client fromClient = fromLoanAccount.client();
|
||||
final Office toOffice = toSavingsAccount.office();
|
||||
final Client toClient = toSavingsAccount.getClient();
|
||||
|
||||
return AccountTransferDetails.loanTosavingsTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toSavingsAccount,
|
||||
transferType);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToLoanTransfer(Loan fromLoanAccount, Loan toLoanAccount, Integer transferType) {
|
||||
final Office fromOffice = fromLoanAccount.getOffice();
|
||||
final Client fromClient = fromLoanAccount.client();
|
||||
final Office toOffice = toLoanAccount.getOffice();
|
||||
final Client toClient = toLoanAccount.client();
|
||||
|
||||
return AccountTransferDetails.loanToLoanTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toLoanAccount,
|
||||
transferType);
|
||||
}
|
||||
|
||||
// --- Current account assembly methods ---
|
||||
|
||||
public AccountTransferDetails assembleCurrentToCurrentTransfer(final JsonCommand command) {
|
||||
final Long fromCurrentAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final CurrentAccount fromCurrentAccount = currentAccountRepository.findById(fromCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("From current account not found: " + fromCurrentAccountId));
|
||||
|
||||
final Long toCurrentAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final CurrentAccount toCurrentAccount = currentAccountRepository.findById(toCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("To current account not found: " + toCurrentAccountId));
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.currentToCurrentTransfer(fromOffice, fromClient, fromCurrentAccount, toOffice, toClient,
|
||||
toCurrentAccount, transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleCurrentToSavingsTransfer(final JsonCommand command) {
|
||||
final Long fromCurrentAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final CurrentAccount fromCurrentAccount = currentAccountRepository.findById(fromCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("From current account not found: " + fromCurrentAccountId));
|
||||
|
||||
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, false);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.currentToSavingsTransfer(fromOffice, fromClient, fromCurrentAccount, toOffice, toClient,
|
||||
toSavingsAccount, transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToCurrentTransfer(final JsonCommand command) {
|
||||
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId, false);
|
||||
|
||||
final Long toCurrentAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final CurrentAccount toCurrentAccount = currentAccountRepository.findById(toCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("To current account not found: " + toCurrentAccountId));
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.savingsToCurrentTransfer(fromOffice, fromClient, fromSavingsAccount, toOffice, toClient,
|
||||
toCurrentAccount, transfertype);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleCurrentToLoanTransfer(final JsonCommand command) {
|
||||
final Long fromCurrentAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
|
||||
final CurrentAccount fromCurrentAccount = currentAccountRepository.findById(fromCurrentAccountId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("From current account not found: " + fromCurrentAccountId));
|
||||
|
||||
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
|
||||
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
|
||||
|
||||
final JsonElement element = command.parsedJson();
|
||||
final Long fromOfficeId = this.fromApiJsonHelper.extractLongNamed(fromOfficeIdParamName, element);
|
||||
final Office fromOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(fromOfficeId);
|
||||
final Long fromClientId = this.fromApiJsonHelper.extractLongNamed(fromClientIdParamName, element);
|
||||
final Client fromClient = this.clientRepository.findOneWithNotFoundDetection(fromClientId);
|
||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||
final Office toOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(toOfficeId);
|
||||
final Long toClientId = this.fromApiJsonHelper.extractLongNamed(toClientIdParamName, element);
|
||||
final Client toClient = this.clientRepository.findOneWithNotFoundDetection(toClientId);
|
||||
final Integer transfertype = this.fromApiJsonHelper.extractIntegerNamed(transferTypeParamName, element, Locale.getDefault());
|
||||
|
||||
return AccountTransferDetails.currentToLoanTransfer(fromOffice, fromClient, fromCurrentAccount, toOffice, toClient,
|
||||
toLoanAccount, transfertype);
|
||||
}
|
||||
}
|
||||
|
||||
+216
-165
@@ -1,165 +1,216 @@
|
||||
/**
|
||||
* 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.account.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.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_account_transfer_details")
|
||||
public class AccountTransferDetails extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_office_id", nullable = false)
|
||||
private Office fromOffice;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_client_id", nullable = false)
|
||||
private Client fromClient;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_savings_account_id", nullable = true)
|
||||
private SavingsAccount fromSavingsAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_office_id", nullable = false)
|
||||
private Office toOffice;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_client_id", nullable = false)
|
||||
private Client toClient;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_savings_account_id", nullable = true)
|
||||
private SavingsAccount toSavingsAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_loan_account_id", nullable = true)
|
||||
private Loan toLoanAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_loan_account_id", nullable = true)
|
||||
private Loan fromLoanAccount;
|
||||
|
||||
@Column(name = "transfer_type")
|
||||
private Integer transferType;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "accountTransferDetails", orphanRemoval = true, fetch = FetchType.EAGER)
|
||||
private List<AccountTransferTransaction> accountTransferTransactions = new ArrayList<>();
|
||||
|
||||
@OneToOne(mappedBy = "accountTransferDetails", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.EAGER)
|
||||
private AccountTransferStandingInstruction accountTransferStandingInstruction;
|
||||
|
||||
public static AccountTransferDetails savingsToSavingsTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||
Integer transferType) {
|
||||
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, toOffice, toClient, toSavingsAccount, null,
|
||||
transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails savingsToLoanTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final Loan toLoanAccount,
|
||||
Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, toOffice, toClient, null, toLoanAccount,
|
||||
transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails loanTosavingsTransfer(final Office fromOffice, final Client fromClient, final Loan fromLoanAccount,
|
||||
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, toOffice, toClient, toSavingsAccount, null,
|
||||
transferType, null);
|
||||
}
|
||||
|
||||
protected AccountTransferDetails() {
|
||||
//
|
||||
}
|
||||
|
||||
private AccountTransferDetails(final Office fromOffice, final Client fromClient, final SavingsAccount fromSavingsAccount,
|
||||
final Loan fromLoanAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||
final Loan toLoanAccount, final Integer transferType,
|
||||
final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
||||
this.fromOffice = fromOffice;
|
||||
this.fromClient = fromClient;
|
||||
this.fromSavingsAccount = fromSavingsAccount;
|
||||
this.fromLoanAccount = fromLoanAccount;
|
||||
this.toOffice = toOffice;
|
||||
this.toClient = toClient;
|
||||
this.toSavingsAccount = toSavingsAccount;
|
||||
this.toLoanAccount = toLoanAccount;
|
||||
this.transferType = transferType;
|
||||
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public SavingsAccount toSavingsAccount() {
|
||||
return this.toSavingsAccount;
|
||||
}
|
||||
|
||||
public SavingsAccount fromSavingsAccount() {
|
||||
return this.fromSavingsAccount;
|
||||
}
|
||||
|
||||
public void addAccountTransferTransaction(AccountTransferTransaction accountTransferTransaction) {
|
||||
this.accountTransferTransactions.add(accountTransferTransaction);
|
||||
}
|
||||
|
||||
public void updateAccountTransferStandingInstruction(final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
||||
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public Loan toLoanAccount() {
|
||||
return this.toLoanAccount;
|
||||
}
|
||||
|
||||
public Loan fromLoanAccount() {
|
||||
return this.fromLoanAccount;
|
||||
}
|
||||
|
||||
public AccountTransferStandingInstruction accountTransferStandingInstruction() {
|
||||
return this.accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public AccountTransferType transferType() {
|
||||
return AccountTransferType.fromInt(this.transferType);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails loanToLoanTransfer(Office fromOffice, Client fromClient, Loan fromLoanAccount, Office toOffice,
|
||||
Client toClient, Loan toLoanAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, toOffice, toClient, null, toLoanAccount,
|
||||
transferType, null);
|
||||
}
|
||||
|
||||
public List<AccountTransferTransaction> getAccountTransferTransactions() {
|
||||
return accountTransferTransactions;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.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.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_account_transfer_details")
|
||||
public class AccountTransferDetails extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_office_id", nullable = false)
|
||||
private Office fromOffice;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_client_id", nullable = false)
|
||||
private Client fromClient;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_savings_account_id", nullable = true)
|
||||
private SavingsAccount fromSavingsAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_office_id", nullable = false)
|
||||
private Office toOffice;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_client_id", nullable = false)
|
||||
private Client toClient;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_savings_account_id", nullable = true)
|
||||
private SavingsAccount toSavingsAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_loan_account_id", nullable = true)
|
||||
private Loan toLoanAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_loan_account_id", nullable = true)
|
||||
private Loan fromLoanAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "from_current_account_id", nullable = true)
|
||||
private CurrentAccount fromCurrentAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "to_current_account_id", nullable = true)
|
||||
private CurrentAccount toCurrentAccount;
|
||||
|
||||
@Column(name = "transfer_type")
|
||||
private Integer transferType;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "accountTransferDetails", orphanRemoval = true, fetch = FetchType.EAGER)
|
||||
private List<AccountTransferTransaction> accountTransferTransactions = new ArrayList<>();
|
||||
|
||||
@OneToOne(mappedBy = "accountTransferDetails", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.EAGER)
|
||||
private AccountTransferStandingInstruction accountTransferStandingInstruction;
|
||||
|
||||
public static AccountTransferDetails savingsToSavingsTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||
Integer transferType) {
|
||||
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, null, toOffice, toClient, toSavingsAccount,
|
||||
null, null, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails savingsToLoanTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final Loan toLoanAccount,
|
||||
Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, null, toOffice, toClient, null, toLoanAccount,
|
||||
null, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails loanTosavingsTransfer(final Office fromOffice, final Client fromClient, final Loan fromLoanAccount,
|
||||
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, null, toOffice, toClient, toSavingsAccount, null,
|
||||
null, transferType, null);
|
||||
}
|
||||
|
||||
protected AccountTransferDetails() {
|
||||
//
|
||||
}
|
||||
|
||||
private AccountTransferDetails(final Office fromOffice, final Client fromClient, final SavingsAccount fromSavingsAccount,
|
||||
final Loan fromLoanAccount, final CurrentAccount fromCurrentAccount,
|
||||
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||
final Loan toLoanAccount, final CurrentAccount toCurrentAccount,
|
||||
final Integer transferType,
|
||||
final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
||||
this.fromOffice = fromOffice;
|
||||
this.fromClient = fromClient;
|
||||
this.fromSavingsAccount = fromSavingsAccount;
|
||||
this.fromLoanAccount = fromLoanAccount;
|
||||
this.fromCurrentAccount = fromCurrentAccount;
|
||||
this.toOffice = toOffice;
|
||||
this.toClient = toClient;
|
||||
this.toSavingsAccount = toSavingsAccount;
|
||||
this.toLoanAccount = toLoanAccount;
|
||||
this.toCurrentAccount = toCurrentAccount;
|
||||
this.transferType = transferType;
|
||||
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public SavingsAccount toSavingsAccount() {
|
||||
return this.toSavingsAccount;
|
||||
}
|
||||
|
||||
public SavingsAccount fromSavingsAccount() {
|
||||
return this.fromSavingsAccount;
|
||||
}
|
||||
|
||||
public void addAccountTransferTransaction(AccountTransferTransaction accountTransferTransaction) {
|
||||
this.accountTransferTransactions.add(accountTransferTransaction);
|
||||
}
|
||||
|
||||
public void updateAccountTransferStandingInstruction(final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
||||
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public Loan toLoanAccount() {
|
||||
return this.toLoanAccount;
|
||||
}
|
||||
|
||||
public Loan fromLoanAccount() {
|
||||
return this.fromLoanAccount;
|
||||
}
|
||||
|
||||
public CurrentAccount fromCurrentAccount() {
|
||||
return this.fromCurrentAccount;
|
||||
}
|
||||
|
||||
public CurrentAccount toCurrentAccount() {
|
||||
return this.toCurrentAccount;
|
||||
}
|
||||
|
||||
public AccountTransferStandingInstruction accountTransferStandingInstruction() {
|
||||
return this.accountTransferStandingInstruction;
|
||||
}
|
||||
|
||||
public AccountTransferType transferType() {
|
||||
return AccountTransferType.fromInt(this.transferType);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails loanToLoanTransfer(Office fromOffice, Client fromClient, Loan fromLoanAccount, Office toOffice,
|
||||
Client toClient, Loan toLoanAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, null, toOffice, toClient, null, toLoanAccount,
|
||||
null, transferType, null);
|
||||
}
|
||||
|
||||
// --- Current account factory methods ---
|
||||
|
||||
public static AccountTransferDetails currentToCurrentTransfer(final Office fromOffice, final Client fromClient,
|
||||
final CurrentAccount fromCurrentAccount, final Office toOffice, final Client toClient,
|
||||
final CurrentAccount toCurrentAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, null, fromCurrentAccount, toOffice, toClient, null, null,
|
||||
toCurrentAccount, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails currentToSavingsTransfer(final Office fromOffice, final Client fromClient,
|
||||
final CurrentAccount fromCurrentAccount, final Office toOffice, final Client toClient,
|
||||
final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, null, fromCurrentAccount, toOffice, toClient, toSavingsAccount,
|
||||
null, null, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails savingsToCurrentTransfer(final Office fromOffice, final Client fromClient,
|
||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient,
|
||||
final CurrentAccount toCurrentAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, null, toOffice, toClient, null, null,
|
||||
toCurrentAccount, transferType, null);
|
||||
}
|
||||
|
||||
public static AccountTransferDetails currentToLoanTransfer(final Office fromOffice, final Client fromClient,
|
||||
final CurrentAccount fromCurrentAccount, final Office toOffice, final Client toClient,
|
||||
final Loan toLoanAccount, Integer transferType) {
|
||||
return new AccountTransferDetails(fromOffice, fromClient, null, null, fromCurrentAccount, toOffice, toClient, null, toLoanAccount,
|
||||
null, transferType, null);
|
||||
}
|
||||
|
||||
public List<AccountTransferTransaction> getAccountTransferTransactions() {
|
||||
return accountTransferTransactions;
|
||||
}
|
||||
}
|
||||
|
||||
+302
-290
@@ -1,290 +1,302 @@
|
||||
/**
|
||||
* 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.account.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.amountParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.instructionTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.priorityParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceFrequencyParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceIntervalParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceOnMonthDayParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validFromParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validTillParamName;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_account_transfer_standing_instructions", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "name" }, name = "name") })
|
||||
public class AccountTransferStandingInstruction extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "account_transfer_details_id", nullable = true)
|
||||
private AccountTransferDetails accountTransferDetails;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
@Column(name = "priority")
|
||||
private Integer priority;
|
||||
|
||||
@Column(name = "instruction_type")
|
||||
private Integer instructionType;
|
||||
|
||||
@Column(name = "status")
|
||||
private Integer status;
|
||||
|
||||
@Column(name = "amount", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal amount;
|
||||
|
||||
@Column(name = "valid_from")
|
||||
private LocalDate validFrom;
|
||||
|
||||
@Column(name = "valid_till")
|
||||
private LocalDate validTill;
|
||||
|
||||
@Column(name = "recurrence_type")
|
||||
private Integer recurrenceType;
|
||||
|
||||
@Column(name = "recurrence_frequency")
|
||||
private Integer recurrenceFrequency;
|
||||
|
||||
@Column(name = "recurrence_interval")
|
||||
private Integer recurrenceInterval;
|
||||
|
||||
@Column(name = "recurrence_on_day")
|
||||
private Integer recurrenceOnDay;
|
||||
|
||||
@Column(name = "recurrence_on_month")
|
||||
private Integer recurrenceOnMonth;
|
||||
|
||||
@Column(name = "last_run_date")
|
||||
private LocalDate latsRunDate;
|
||||
|
||||
protected AccountTransferStandingInstruction() {
|
||||
|
||||
}
|
||||
|
||||
public static AccountTransferStandingInstruction create(final AccountTransferDetails accountTransferDetails, final String name,
|
||||
final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final MonthDay recurrenceOnMonthDay) {
|
||||
Integer recurrenceOnDay = null;
|
||||
Integer recurrenceOnMonth = null;
|
||||
if (recurrenceOnMonthDay != null) {
|
||||
recurrenceOnDay = recurrenceOnMonthDay.getDayOfMonth();
|
||||
recurrenceOnMonth = recurrenceOnMonthDay.getMonthValue();
|
||||
}
|
||||
return new AccountTransferStandingInstruction(accountTransferDetails, name, priority, instructionType, status, amount, validFrom,
|
||||
validTill, recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnDay, recurrenceOnMonth);
|
||||
}
|
||||
|
||||
private AccountTransferStandingInstruction(final AccountTransferDetails accountTransferDetails, final String name,
|
||||
final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final Integer recurrenceOnDay, final Integer recurrenceOnMonth) {
|
||||
this.accountTransferDetails = accountTransferDetails;
|
||||
this.name = name;
|
||||
this.priority = priority;
|
||||
this.instructionType = instructionType;
|
||||
this.status = status;
|
||||
this.amount = amount;
|
||||
this.validFrom = validFrom;
|
||||
this.validTill = validTill;
|
||||
this.recurrenceType = recurrenceType;
|
||||
this.recurrenceFrequency = recurrenceFrequency;
|
||||
this.recurrenceInterval = recurrenceInterval;
|
||||
this.recurrenceOnDay = recurrenceOnDay;
|
||||
this.recurrenceOnMonth = recurrenceOnMonth;
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
validateDependencies(baseDataValidator);
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> update(JsonCommand command) {
|
||||
final Map<String, Object> actualChanges = new HashMap<>();
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
if (StandingInstructionStatus.fromInt(this.status).isDeleted()) {
|
||||
baseDataValidator.reset().parameter(statusParamName).failWithCode("can.not.modify.once.deleted");
|
||||
}
|
||||
|
||||
if (command.isChangeInDateParameterNamed(validFromParamName, this.validFrom)) {
|
||||
this.validFrom = command.localDateValueOfParameterNamed(validFromParamName);
|
||||
actualChanges.put(validFromParamName, this.validFrom);
|
||||
}
|
||||
|
||||
if (command.isChangeInDateParameterNamed(validTillParamName, this.validTill)) {
|
||||
this.validTill = command.localDateValueOfParameterNamed(validTillParamName);
|
||||
actualChanges.put(validTillParamName, this.validTill);
|
||||
}
|
||||
|
||||
if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
|
||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
|
||||
actualChanges.put(amountParamName, newValue);
|
||||
this.amount = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(statusParamName, this.status)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(statusParamName);
|
||||
actualChanges.put(statusParamName, newValue);
|
||||
this.status = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(priorityParamName, this.priority)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(priorityParamName);
|
||||
actualChanges.put(priorityParamName, newValue);
|
||||
this.priority = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(instructionTypeParamName, this.instructionType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(instructionTypeParamName);
|
||||
actualChanges.put(instructionTypeParamName, newValue);
|
||||
this.instructionType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceTypeParamName, this.recurrenceType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceTypeParamName);
|
||||
actualChanges.put(recurrenceTypeParamName, newValue);
|
||||
this.recurrenceType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceFrequencyParamName, this.recurrenceFrequency)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
|
||||
actualChanges.put(recurrenceFrequencyParamName, newValue);
|
||||
this.recurrenceFrequency = newValue;
|
||||
}
|
||||
|
||||
if (command.hasParameter(recurrenceOnMonthDayParamName)) {
|
||||
final MonthDay monthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
|
||||
final String actualValueEntered = command.stringValueOfParameterNamed(recurrenceOnMonthDayParamName);
|
||||
final Integer dayOfMonthValue = monthDay.getDayOfMonth();
|
||||
if (!this.recurrenceOnDay.equals(dayOfMonthValue)) {
|
||||
actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
|
||||
this.recurrenceOnDay = dayOfMonthValue;
|
||||
}
|
||||
|
||||
final Integer monthOfYear = monthDay.getMonthValue();
|
||||
if (!this.recurrenceOnMonth.equals(monthOfYear)) {
|
||||
actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
|
||||
this.recurrenceOnMonth = monthOfYear;
|
||||
}
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceIntervalParamName, this.recurrenceInterval)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
|
||||
actualChanges.put(recurrenceIntervalParamName, newValue);
|
||||
this.recurrenceInterval = newValue;
|
||||
}
|
||||
validateDependencies(baseDataValidator);
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
private void validateDependencies(final DataValidatorBuilder baseDataValidator) {
|
||||
|
||||
if (this.validTill != null && this.validFrom != null) {
|
||||
baseDataValidator.reset().parameter(validTillParamName).value(this.validTill).validateDateAfter(this.validFrom);
|
||||
}
|
||||
|
||||
if (AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence()) {
|
||||
baseDataValidator.reset().parameter(recurrenceFrequencyParamName).value(this.recurrenceFrequency).notNull();
|
||||
baseDataValidator.reset().parameter(recurrenceIntervalParamName).value(this.recurrenceInterval).notNull();
|
||||
if (this.recurrenceFrequency != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(this.recurrenceFrequency);
|
||||
if (frequencyType.isMonthly()) {
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
|
||||
} else if (frequencyType.isYearly()) {
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnMonth).notNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.accountTransferDetails.toSavingsAccount() != null) {
|
||||
baseDataValidator.reset().parameter(instructionTypeParamName).value(this.instructionType).notNull().inMinMaxRange(1, 1);
|
||||
baseDataValidator.reset().parameter(recurrenceTypeParamName).value(this.recurrenceType).notNull().inMinMaxRange(1, 1);
|
||||
}
|
||||
|
||||
if (StandingInstructionType.fromInt(this.instructionType).isFixedAmoutTransfer()) {
|
||||
baseDataValidator.reset().parameter(amountParamName).value(this.amount).notNull();
|
||||
}
|
||||
|
||||
String errorCode = null;
|
||||
if (this.accountTransferDetails.transferType().isAccountTransfer()
|
||||
&& (this.accountTransferDetails.fromSavingsAccount() == null || this.accountTransferDetails.toSavingsAccount() == null)) {
|
||||
errorCode = "not.account.transfer";
|
||||
} else if (this.accountTransferDetails.transferType().isLoanRepayment()
|
||||
&& (this.accountTransferDetails.fromSavingsAccount() == null || this.accountTransferDetails.toLoanAccount() == null)) {
|
||||
errorCode = "not.loan.repayment";
|
||||
}
|
||||
if (errorCode != null) {
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void updateLatsRunDate(LocalDate latsRunDate) {
|
||||
this.latsRunDate = latsRunDate;
|
||||
}
|
||||
|
||||
public void updateStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete the standing instruction by setting the status to 3 and appending "_deleted_" and the id to the name
|
||||
**/
|
||||
public void delete() {
|
||||
this.status = StandingInstructionStatus.DELETED.getValue();
|
||||
this.name = this.name + "_deleted_" + this.getId();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.transferTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.STANDING_INSTRUCTION_RESOURCE_NAME;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.amountParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.instructionTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.priorityParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceFrequencyParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceIntervalParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceOnMonthDayParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validFromParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validTillParamName;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_account_transfer_standing_instructions", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = { "name" }, name = "name") })
|
||||
public class AccountTransferStandingInstruction extends AbstractPersistableCustom<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "account_transfer_details_id", nullable = true)
|
||||
private AccountTransferDetails accountTransferDetails;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
@Column(name = "priority")
|
||||
private Integer priority;
|
||||
|
||||
@Column(name = "instruction_type")
|
||||
private Integer instructionType;
|
||||
|
||||
@Column(name = "status")
|
||||
private Integer status;
|
||||
|
||||
@Column(name = "amount", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal amount;
|
||||
|
||||
@Column(name = "valid_from")
|
||||
private LocalDate validFrom;
|
||||
|
||||
@Column(name = "valid_till")
|
||||
private LocalDate validTill;
|
||||
|
||||
@Column(name = "recurrence_type")
|
||||
private Integer recurrenceType;
|
||||
|
||||
@Column(name = "recurrence_frequency")
|
||||
private Integer recurrenceFrequency;
|
||||
|
||||
@Column(name = "recurrence_interval")
|
||||
private Integer recurrenceInterval;
|
||||
|
||||
@Column(name = "recurrence_on_day")
|
||||
private Integer recurrenceOnDay;
|
||||
|
||||
@Column(name = "recurrence_on_month")
|
||||
private Integer recurrenceOnMonth;
|
||||
|
||||
@Column(name = "last_run_date")
|
||||
private LocalDate latsRunDate;
|
||||
|
||||
protected AccountTransferStandingInstruction() {
|
||||
|
||||
}
|
||||
|
||||
public static AccountTransferStandingInstruction create(final AccountTransferDetails accountTransferDetails, final String name,
|
||||
final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final MonthDay recurrenceOnMonthDay) {
|
||||
Integer recurrenceOnDay = null;
|
||||
Integer recurrenceOnMonth = null;
|
||||
if (recurrenceOnMonthDay != null) {
|
||||
recurrenceOnDay = recurrenceOnMonthDay.getDayOfMonth();
|
||||
recurrenceOnMonth = recurrenceOnMonthDay.getMonthValue();
|
||||
}
|
||||
return new AccountTransferStandingInstruction(accountTransferDetails, name, priority, instructionType, status, amount, validFrom,
|
||||
validTill, recurrenceType, recurrenceFrequency, recurrenceInterval, recurrenceOnDay, recurrenceOnMonth);
|
||||
}
|
||||
|
||||
private AccountTransferStandingInstruction(final AccountTransferDetails accountTransferDetails, final String name,
|
||||
final Integer priority, final Integer instructionType, final Integer status, final BigDecimal amount, final LocalDate validFrom,
|
||||
final LocalDate validTill, final Integer recurrenceType, final Integer recurrenceFrequency, final Integer recurrenceInterval,
|
||||
final Integer recurrenceOnDay, final Integer recurrenceOnMonth) {
|
||||
this.accountTransferDetails = accountTransferDetails;
|
||||
this.name = name;
|
||||
this.priority = priority;
|
||||
this.instructionType = instructionType;
|
||||
this.status = status;
|
||||
this.amount = amount;
|
||||
this.validFrom = validFrom;
|
||||
this.validTill = validTill;
|
||||
this.recurrenceType = recurrenceType;
|
||||
this.recurrenceFrequency = recurrenceFrequency;
|
||||
this.recurrenceInterval = recurrenceInterval;
|
||||
this.recurrenceOnDay = recurrenceOnDay;
|
||||
this.recurrenceOnMonth = recurrenceOnMonth;
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
validateDependencies(baseDataValidator);
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> update(JsonCommand command) {
|
||||
final Map<String, Object> actualChanges = new HashMap<>();
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||
.resource(STANDING_INSTRUCTION_RESOURCE_NAME);
|
||||
|
||||
if (StandingInstructionStatus.fromInt(this.status).isDeleted()) {
|
||||
baseDataValidator.reset().parameter(statusParamName).failWithCode("can.not.modify.once.deleted");
|
||||
}
|
||||
|
||||
if (command.isChangeInDateParameterNamed(validFromParamName, this.validFrom)) {
|
||||
this.validFrom = command.localDateValueOfParameterNamed(validFromParamName);
|
||||
actualChanges.put(validFromParamName, this.validFrom);
|
||||
}
|
||||
|
||||
if (command.isChangeInDateParameterNamed(validTillParamName, this.validTill)) {
|
||||
this.validTill = command.localDateValueOfParameterNamed(validTillParamName);
|
||||
actualChanges.put(validTillParamName, this.validTill);
|
||||
}
|
||||
|
||||
if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
|
||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
|
||||
actualChanges.put(amountParamName, newValue);
|
||||
this.amount = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(statusParamName, this.status)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(statusParamName);
|
||||
actualChanges.put(statusParamName, newValue);
|
||||
this.status = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(priorityParamName, this.priority)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(priorityParamName);
|
||||
actualChanges.put(priorityParamName, newValue);
|
||||
this.priority = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(instructionTypeParamName, this.instructionType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(instructionTypeParamName);
|
||||
actualChanges.put(instructionTypeParamName, newValue);
|
||||
this.instructionType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceTypeParamName, this.recurrenceType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceTypeParamName);
|
||||
actualChanges.put(recurrenceTypeParamName, newValue);
|
||||
this.recurrenceType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceFrequencyParamName, this.recurrenceFrequency)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
|
||||
actualChanges.put(recurrenceFrequencyParamName, newValue);
|
||||
this.recurrenceFrequency = newValue;
|
||||
}
|
||||
|
||||
if (command.hasParameter(recurrenceOnMonthDayParamName)) {
|
||||
final MonthDay monthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
|
||||
final String actualValueEntered = command.stringValueOfParameterNamed(recurrenceOnMonthDayParamName);
|
||||
final Integer dayOfMonthValue = monthDay.getDayOfMonth();
|
||||
if (!this.recurrenceOnDay.equals(dayOfMonthValue)) {
|
||||
actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
|
||||
this.recurrenceOnDay = dayOfMonthValue;
|
||||
}
|
||||
|
||||
final Integer monthOfYear = monthDay.getMonthValue();
|
||||
if (!this.recurrenceOnMonth.equals(monthOfYear)) {
|
||||
actualChanges.put(recurrenceOnMonthDayParamName, actualValueEntered);
|
||||
this.recurrenceOnMonth = monthOfYear;
|
||||
}
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(recurrenceIntervalParamName, this.recurrenceInterval)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
|
||||
actualChanges.put(recurrenceIntervalParamName, newValue);
|
||||
this.recurrenceInterval = newValue;
|
||||
}
|
||||
validateDependencies(baseDataValidator);
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
private void validateDependencies(final DataValidatorBuilder baseDataValidator) {
|
||||
|
||||
if (this.validTill != null && this.validFrom != null) {
|
||||
baseDataValidator.reset().parameter(validTillParamName).value(this.validTill).validateDateAfter(this.validFrom);
|
||||
}
|
||||
|
||||
if (AccountTransferRecurrenceType.fromInt(recurrenceType).isPeriodicRecurrence()) {
|
||||
baseDataValidator.reset().parameter(recurrenceFrequencyParamName).value(this.recurrenceFrequency).notNull();
|
||||
baseDataValidator.reset().parameter(recurrenceIntervalParamName).value(this.recurrenceInterval).notNull();
|
||||
if (this.recurrenceFrequency != null) {
|
||||
PeriodFrequencyType frequencyType = PeriodFrequencyType.fromInt(this.recurrenceFrequency);
|
||||
if (frequencyType.isMonthly()) {
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
|
||||
} else if (frequencyType.isYearly()) {
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnDay).notNull();
|
||||
baseDataValidator.reset().parameter(recurrenceOnMonthDayParamName).value(this.recurrenceOnMonth).notNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For savings-to-savings or current-to-savings: enforce Fixed instruction + Periodic recurrence
|
||||
if (this.accountTransferDetails.toSavingsAccount() != null
|
||||
|| this.accountTransferDetails.toCurrentAccount() != null) {
|
||||
baseDataValidator.reset().parameter(instructionTypeParamName).value(this.instructionType).notNull().inMinMaxRange(1, 1);
|
||||
baseDataValidator.reset().parameter(recurrenceTypeParamName).value(this.recurrenceType).notNull().inMinMaxRange(1, 1);
|
||||
}
|
||||
|
||||
if (StandingInstructionType.fromInt(this.instructionType).isFixedAmoutTransfer()) {
|
||||
baseDataValidator.reset().parameter(amountParamName).value(this.amount).notNull();
|
||||
}
|
||||
|
||||
// Account Transfer: from must be savings OR current; to must be savings OR current
|
||||
boolean fromAccountValid = this.accountTransferDetails.fromSavingsAccount() != null
|
||||
|| this.accountTransferDetails.fromCurrentAccount() != null;
|
||||
boolean toAccountValid = this.accountTransferDetails.toSavingsAccount() != null
|
||||
|| this.accountTransferDetails.toCurrentAccount() != null;
|
||||
|
||||
// Loan Repayment: from must be savings OR current; to must be loan
|
||||
boolean fromAccountValidForLoan = this.accountTransferDetails.fromSavingsAccount() != null
|
||||
|| this.accountTransferDetails.fromCurrentAccount() != null;
|
||||
|
||||
String errorCode = null;
|
||||
if (this.accountTransferDetails.transferType().isAccountTransfer()
|
||||
&& !(fromAccountValid && toAccountValid)) {
|
||||
errorCode = "not.account.transfer";
|
||||
} else if (this.accountTransferDetails.transferType().isLoanRepayment()
|
||||
&& !(fromAccountValidForLoan && this.accountTransferDetails.toLoanAccount() != null)) {
|
||||
errorCode = "not.loan.repayment";
|
||||
}
|
||||
if (errorCode != null) {
|
||||
baseDataValidator.reset().parameter(transferTypeParamName).failWithCode(errorCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void updateLatsRunDate(LocalDate latsRunDate) {
|
||||
this.latsRunDate = latsRunDate;
|
||||
}
|
||||
|
||||
public void updateStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete the standing instruction by setting the status to 3 and appending "_deleted_" and the id to the name
|
||||
**/
|
||||
public void delete() {
|
||||
this.status = StandingInstructionStatus.DELETED.getValue();
|
||||
this.name = this.name + "_deleted_" + this.getId();
|
||||
}
|
||||
}
|
||||
|
||||
+143
-93
@@ -1,93 +1,143 @@
|
||||
/**
|
||||
* 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.account.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.amountParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.instructionTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.nameParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.priorityParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceFrequencyParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceIntervalParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceOnMonthDayParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validFromParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validTillParamName;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class StandingInstructionAssembler {
|
||||
|
||||
private final AccountTransferDetailAssembler accountTransferDetailAssembler;
|
||||
|
||||
@Autowired
|
||||
public StandingInstructionAssembler(final AccountTransferDetailAssembler accountTransferDetailAssembler) {
|
||||
|
||||
this.accountTransferDetailAssembler = accountTransferDetailAssembler;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public void assembleStandingInstruction(final JsonCommand command, final AccountTransferDetails accountTransferDetails) {
|
||||
final LocalDate validFrom = command.localDateValueOfParameterNamed(validFromParamName);
|
||||
final LocalDate validTill = command.localDateValueOfParameterNamed(validTillParamName);
|
||||
BigDecimal amount = null;
|
||||
final BigDecimal transferAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
|
||||
if (transferAmount != null) {
|
||||
final Money monetaryAmount = Money.of(accountTransferDetails.fromSavingsAccount().getCurrency(), transferAmount);
|
||||
amount = monetaryAmount.getAmount();
|
||||
}
|
||||
final Integer status = command.integerValueOfParameterNamed(statusParamName);
|
||||
final Integer priority = command.integerValueOfParameterNamed(priorityParamName);
|
||||
final Integer standingInstructionType = command.integerValueOfParameterNamed(instructionTypeParamName);
|
||||
final Integer recurrenceType = command.integerValueOfParameterNamed(recurrenceTypeParamName);
|
||||
final Integer recurrenceFrequency = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
|
||||
final MonthDay recurrenceOnMonthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
|
||||
final Integer recurrenceInterval = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
|
||||
final String name = command.stringValueOfParameterNamed(nameParamName);
|
||||
AccountTransferStandingInstruction accountTransferStandingInstruction = AccountTransferStandingInstruction.create(
|
||||
accountTransferDetails, name, priority, standingInstructionType, status, amount, validFrom, validTill, recurrenceType,
|
||||
recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay);
|
||||
accountTransferDetails.updateAccountTransferStandingInstruction(accountTransferStandingInstruction);
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleLoanToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.account.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.amountParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.instructionTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.nameParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.priorityParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceFrequencyParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceIntervalParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceOnMonthDayParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.recurrenceTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validFromParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.validTillParamName;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.MonthDay;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class StandingInstructionAssembler {
|
||||
|
||||
private final AccountTransferDetailAssembler accountTransferDetailAssembler;
|
||||
|
||||
@Autowired
|
||||
public StandingInstructionAssembler(final AccountTransferDetailAssembler accountTransferDetailAssembler) {
|
||||
|
||||
this.accountTransferDetailAssembler = accountTransferDetailAssembler;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public void assembleStandingInstruction(final JsonCommand command, final AccountTransferDetails accountTransferDetails) {
|
||||
final LocalDate validFrom = command.localDateValueOfParameterNamed(validFromParamName);
|
||||
final LocalDate validTill = command.localDateValueOfParameterNamed(validTillParamName);
|
||||
BigDecimal amount = null;
|
||||
final BigDecimal transferAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
|
||||
if (transferAmount != null) {
|
||||
final MonetaryCurrency currency = resolveCurrency(accountTransferDetails);
|
||||
if (currency != null) {
|
||||
final Money monetaryAmount = Money.of(currency, transferAmount);
|
||||
amount = monetaryAmount.getAmount();
|
||||
} else {
|
||||
amount = transferAmount;
|
||||
}
|
||||
}
|
||||
final Integer status = command.integerValueOfParameterNamed(statusParamName);
|
||||
final Integer priority = command.integerValueOfParameterNamed(priorityParamName);
|
||||
final Integer standingInstructionType = command.integerValueOfParameterNamed(instructionTypeParamName);
|
||||
final Integer recurrenceType = command.integerValueOfParameterNamed(recurrenceTypeParamName);
|
||||
final Integer recurrenceFrequency = command.integerValueOfParameterNamed(recurrenceFrequencyParamName);
|
||||
final MonthDay recurrenceOnMonthDay = command.extractMonthDayNamed(recurrenceOnMonthDayParamName);
|
||||
final Integer recurrenceInterval = command.integerValueOfParameterNamed(recurrenceIntervalParamName);
|
||||
final String name = command.stringValueOfParameterNamed(nameParamName);
|
||||
AccountTransferStandingInstruction accountTransferStandingInstruction = AccountTransferStandingInstruction.create(
|
||||
accountTransferDetails, name, priority, standingInstructionType, status, amount, validFrom, validTill, recurrenceType,
|
||||
recurrenceFrequency, recurrenceInterval, recurrenceOnMonthDay);
|
||||
accountTransferDetails.updateAccountTransferStandingInstruction(accountTransferStandingInstruction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the currency from the "from" account — savings, loan (null), or current account.
|
||||
*/
|
||||
private MonetaryCurrency resolveCurrency(final AccountTransferDetails details) {
|
||||
final SavingsAccount fromSavings = details.fromSavingsAccount();
|
||||
if (fromSavings != null) {
|
||||
return fromSavings.getCurrency();
|
||||
}
|
||||
final CurrentAccount fromCurrent = details.fromCurrentAccount();
|
||||
if (fromCurrent != null) {
|
||||
return new MonetaryCurrency(fromCurrent.getCurrencyCode(), fromCurrent.getCurrencyDigits(),
|
||||
fromCurrent.getCurrencyMultiplesOf());
|
||||
}
|
||||
return null; // loan accounts don't have a direct currency on AccountTransferDetails
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleLoanToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleLoanToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
// --- Current account methods ---
|
||||
|
||||
public AccountTransferDetails assembleCurrentToCurrentTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleCurrentToCurrentTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleCurrentToSavingsTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleCurrentToSavingsTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleSavingsToCurrentTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToCurrentTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
public AccountTransferDetails assembleCurrentToLoanTransfer(final JsonCommand command) {
|
||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleCurrentToLoanTransfer(command);
|
||||
assembleStandingInstruction(command, accountTransferDetails);
|
||||
return accountTransferDetails;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.account.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTerminateWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "ACCOUNT", action = "TERMINATE")
|
||||
public class AccountTerminateCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final AccountTerminateWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public AccountTerminateCommandHandler(final AccountTerminateWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.terminate(command.entityId(), command);
|
||||
}
|
||||
}
|
||||
+73
-70
@@ -1,70 +1,73 @@
|
||||
/**
|
||||
* 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.account.jobs.executestandinginstructions;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
@Configuration
|
||||
public class ExecuteStandingInstructionsConfig {
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Autowired
|
||||
private StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
@Autowired
|
||||
private DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
@Autowired
|
||||
private AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||
|
||||
@Bean
|
||||
protected Step executeStandingInstructionsStep() {
|
||||
return new StepBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository)
|
||||
.tasklet(executeStandingInstructionsTasklet(), transactionManager).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Job executeStandingInstructionsJob() {
|
||||
return new JobBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository).start(executeStandingInstructionsStep())
|
||||
.incrementer(new RunIdIncrementer()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() {
|
||||
return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator,
|
||||
accountTransfersWritePlatformService);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.jobs.executestandinginstructions;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
@Configuration
|
||||
public class ExecuteStandingInstructionsConfig {
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Autowired
|
||||
private StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
@Autowired
|
||||
private DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
@Autowired
|
||||
private AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||
@Autowired
|
||||
private AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
|
||||
@Bean
|
||||
protected Step executeStandingInstructionsStep() {
|
||||
return new StepBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository)
|
||||
.tasklet(executeStandingInstructionsTasklet(), transactionManager).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Job executeStandingInstructionsJob() {
|
||||
return new JobBuilder(JobName.EXECUTE_STANDING_INSTRUCTIONS.name(), jobRepository).start(executeStandingInstructionsStep())
|
||||
.incrementer(new RunIdIncrementer()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() {
|
||||
return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator,
|
||||
accountTransfersWritePlatformService, accountTransferDetailRepository);
|
||||
}
|
||||
}
|
||||
|
||||
+180
-172
@@ -1,172 +1,180 @@
|
||||
/**
|
||||
* 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.account.jobs.executestandinginstructions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ExecuteStandingInstructionsTasklet implements Tasklet {
|
||||
|
||||
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
Collection<StandingInstructionData> instructionData = standingInstructionReadPlatformService
|
||||
.retrieveAll(StandingInstructionStatus.ACTIVE.getValue());
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (StandingInstructionData data : instructionData) {
|
||||
boolean isDueForTransfer = false;
|
||||
AccountTransferRecurrenceType recurrenceType = data.getRecurrenceType();
|
||||
StandingInstructionType instructionType = data.getInstructionType();
|
||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||
if (recurrenceType.isPeriodicRecurrence()) {
|
||||
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||
PeriodFrequencyType frequencyType = data.getRecurrenceFrequency();
|
||||
LocalDate startDate = data.getValidFrom();
|
||||
if (frequencyType.isMonthly()) {
|
||||
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay());
|
||||
if (DateUtils.isBefore(startDate, data.getValidFrom())) {
|
||||
startDate = startDate.plusMonths(1);
|
||||
}
|
||||
} else if (frequencyType.isYearly()) {
|
||||
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay()).withMonth(data.getRecurrenceOnMonth());
|
||||
if (DateUtils.isBefore(startDate, data.getValidFrom())) {
|
||||
startDate = startDate.plusYears(1);
|
||||
}
|
||||
}
|
||||
isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType, data.getRecurrenceInterval(), startDate,
|
||||
transactionDate);
|
||||
|
||||
}
|
||||
BigDecimal transactionAmount = data.getAmount();
|
||||
if (data.getToAccountType().isLoanAccount()
|
||||
&& (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
|
||||
StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService
|
||||
.retriveLoanDuesData(data.getToAccount().getId());
|
||||
if (data.getInstructionType().isDuesAmoutTransfer()) {
|
||||
transactionAmount = standingInstructionDuesData.totalDueAmount();
|
||||
}
|
||||
if (recurrenceType.isDuesRecurrence()) {
|
||||
isDueForTransfer = isDueForTransfer(standingInstructionDuesData);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDueForTransfer && transactionAmount != null && transactionAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
final SavingsAccount fromSavingsAccount = null;
|
||||
final boolean isRegularTransaction = true;
|
||||
final boolean isExceptionForBalanceCheck = false;
|
||||
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount,
|
||||
data.getFromAccountType(), data.getToAccountType(), data.getFromAccount().getId(), data.getToAccount().getId(),
|
||||
data.getName() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
|
||||
data.getTransferType().getValue(), null, null, ExternalId.empty(), null, null, fromSavingsAccount,
|
||||
isRegularTransaction, isExceptionForBalanceCheck);
|
||||
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());
|
||||
|
||||
if (transferCompleted) {
|
||||
final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?";
|
||||
jdbcTemplate.update(updateQuery, transactionDate, data.getId());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw new JobExecutionException(errors);
|
||||
}
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
private boolean transferAmount(final List<Throwable> errors, final AccountTransferDTO accountTransferDTO, final Long instructionId) {
|
||||
boolean transferCompleted = true;
|
||||
StringBuilder errorLog = new StringBuilder();
|
||||
StringBuilder updateQuery = new StringBuilder(
|
||||
"INSERT INTO m_account_transfer_standing_instructions_history (standing_instruction_id, " + sqlGenerator.escape("status")
|
||||
+ ", amount,execution_time, error_log) VALUES (");
|
||||
try {
|
||||
accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
} catch (final PlatformApiDataValidationException e) {
|
||||
errors.add(new Exception("Validation exception while transfering funds for standing Instruction id" + instructionId + " from "
|
||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Validation exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||
} catch (final InsufficientAccountBalanceException e) {
|
||||
errors.add(new Exception("InsufficientAccountBalance Exception while trasfering funds for standing Instruction id"
|
||||
+ instructionId + " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("InsufficientAccountBalance Exception ");
|
||||
} catch (final AbstractPlatformServiceUnavailableException e) {
|
||||
errors.add(new Exception("Platform exception while trasfering funds for standing Instruction id" + instructionId + " from "
|
||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Platform exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||
} catch (Exception e) {
|
||||
errors.add(new Exception("Unhandled System Exception while trasfering funds for standing Instruction id" + instructionId
|
||||
+ " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Exception while trasfering funds ").append(e.getMessage());
|
||||
|
||||
}
|
||||
updateQuery.append(instructionId).append(",");
|
||||
if (errorLog.length() > 0) {
|
||||
transferCompleted = false;
|
||||
updateQuery.append("'failed'").append(",");
|
||||
} else {
|
||||
updateQuery.append("'success'").append(",");
|
||||
}
|
||||
updateQuery.append(accountTransferDTO.getTransactionAmount().doubleValue());
|
||||
updateQuery.append(", now(),");
|
||||
updateQuery.append("'").append(errorLog).append("')");
|
||||
jdbcTemplate.update(updateQuery.toString());
|
||||
return transferCompleted;
|
||||
}
|
||||
|
||||
public boolean isDueForTransfer(StandingInstructionDuesData standingInstructionDuesData) {
|
||||
return standingInstructionDuesData.dueDate() != null
|
||||
&& !standingInstructionDuesData.dueDate().isAfter(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.jobs.executestandinginstructions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ExecuteStandingInstructionsTasklet implements Tasklet {
|
||||
|
||||
private final StandingInstructionReadPlatformService standingInstructionReadPlatformService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
Collection<StandingInstructionData> instructionData = standingInstructionReadPlatformService
|
||||
.retrieveAll(StandingInstructionStatus.ACTIVE.getValue());
|
||||
List<Throwable> errors = new ArrayList<>();
|
||||
for (StandingInstructionData data : instructionData) {
|
||||
boolean isDueForTransfer = false;
|
||||
AccountTransferRecurrenceType recurrenceType = data.recurrenceTypeEnum();
|
||||
StandingInstructionType instructionType = data.instructionTypeEnum();
|
||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||
if (recurrenceType.isPeriodicRecurrence()) {
|
||||
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||
PeriodFrequencyType frequencyType = data.recurrenceFrequencyEnum();
|
||||
LocalDate startDate = data.getValidFrom();
|
||||
if (frequencyType.isMonthly()) {
|
||||
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay());
|
||||
if (DateUtils.isBefore(startDate, data.getValidFrom())) {
|
||||
startDate = startDate.plusMonths(1);
|
||||
}
|
||||
} else if (frequencyType.isYearly()) {
|
||||
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay()).withMonth(data.getRecurrenceOnMonth());
|
||||
if (DateUtils.isBefore(startDate, data.getValidFrom())) {
|
||||
startDate = startDate.plusYears(1);
|
||||
}
|
||||
}
|
||||
isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType, data.getRecurrenceInterval(), startDate,
|
||||
transactionDate);
|
||||
|
||||
}
|
||||
BigDecimal transactionAmount = data.getAmount();
|
||||
if (data.toAccountTypeEnum() != null && data.toAccountTypeEnum().isLoanAccount()
|
||||
&& (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
|
||||
StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService
|
||||
.retriveLoanDuesData(data.getToAccount().getId());
|
||||
if (data.instructionTypeEnum().isDuesAmoutTransfer()) {
|
||||
transactionAmount = standingInstructionDuesData.totalDueAmount();
|
||||
}
|
||||
if (recurrenceType.isDuesRecurrence()) {
|
||||
isDueForTransfer = isDueForTransfer(standingInstructionDuesData);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDueForTransfer && transactionAmount != null && transactionAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
final SavingsAccount fromSavingsAccount = null;
|
||||
final boolean isRegularTransaction = true;
|
||||
final boolean isExceptionForBalanceCheck = false;
|
||||
// Load existing AccountTransferDetails so transactions are linked to
|
||||
// this standing instruction and appear in its transaction history
|
||||
final AccountTransferDetails existingDetails = data.getAccountDetailId() != null
|
||||
? accountTransferDetailRepository.findById(data.getAccountDetailId()).orElse(null)
|
||||
: null;
|
||||
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount,
|
||||
data.fromAccountTypeEnum(), data.toAccountTypeEnum(), data.getFromAccount().getId(), data.getToAccount().getId(),
|
||||
data.getName() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
|
||||
data.transferTypeEnum().getValue(), existingDetails, null, ExternalId.empty(), null, null, fromSavingsAccount,
|
||||
isRegularTransaction, isExceptionForBalanceCheck);
|
||||
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());
|
||||
|
||||
if (transferCompleted) {
|
||||
final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?";
|
||||
jdbcTemplate.update(updateQuery, transactionDate, data.getId());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw new JobExecutionException(errors);
|
||||
}
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
private boolean transferAmount(final List<Throwable> errors, final AccountTransferDTO accountTransferDTO, final Long instructionId) {
|
||||
boolean transferCompleted = true;
|
||||
StringBuilder errorLog = new StringBuilder();
|
||||
StringBuilder updateQuery = new StringBuilder(
|
||||
"INSERT INTO m_account_transfer_standing_instructions_history (standing_instruction_id, " + sqlGenerator.escape("status")
|
||||
+ ", amount,execution_time, error_log) VALUES (");
|
||||
try {
|
||||
accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
} catch (final PlatformApiDataValidationException e) {
|
||||
errors.add(new Exception("Validation exception while transfering funds for standing Instruction id" + instructionId + " from "
|
||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Validation exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||
} catch (final InsufficientAccountBalanceException e) {
|
||||
errors.add(new Exception("InsufficientAccountBalance Exception while trasfering funds for standing Instruction id"
|
||||
+ instructionId + " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("InsufficientAccountBalance Exception ");
|
||||
} catch (final AbstractPlatformServiceUnavailableException e) {
|
||||
errors.add(new Exception("Platform exception while trasfering funds for standing Instruction id" + instructionId + " from "
|
||||
+ accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Platform exception while trasfering funds ").append(e.getDefaultUserMessage());
|
||||
} catch (Exception e) {
|
||||
errors.add(new Exception("Unhandled System Exception while trasfering funds for standing Instruction id" + instructionId
|
||||
+ " from " + accountTransferDTO.getFromAccountId() + " to " + accountTransferDTO.getToAccountId(), e));
|
||||
errorLog.append("Exception while trasfering funds ").append(e.getMessage());
|
||||
|
||||
}
|
||||
updateQuery.append(instructionId).append(",");
|
||||
if (errorLog.length() > 0) {
|
||||
transferCompleted = false;
|
||||
updateQuery.append("'failed'").append(",");
|
||||
} else {
|
||||
updateQuery.append("'success'").append(",");
|
||||
}
|
||||
updateQuery.append(accountTransferDTO.getTransactionAmount().doubleValue());
|
||||
updateQuery.append(", now(),");
|
||||
updateQuery.append("'").append(errorLog).append("')");
|
||||
jdbcTemplate.update(updateQuery.toString());
|
||||
return transferCompleted;
|
||||
}
|
||||
|
||||
public boolean isDueForTransfer(StandingInstructionDuesData standingInstructionDuesData) {
|
||||
return standingInstructionDuesData.dueDate() != null
|
||||
&& !standingInstructionDuesData.dueDate().isAfter(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||
}
|
||||
}
|
||||
|
||||
+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.account.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface AccountTerminateWritePlatformService {
|
||||
|
||||
CommandProcessingResult terminate(Long accountId, JsonCommand command);
|
||||
}
|
||||
+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.account.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Routes terminate to the correct account service based on the
|
||||
* "accountType" field in the JSON body:
|
||||
* SAVINGS → m_savings_account (depositType = savings)
|
||||
* FD → m_savings_account (depositType = 200)
|
||||
* RD → m_savings_account (depositType = 300)
|
||||
* CURRENT → m_current_account
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AccountTerminateWritePlatformServiceImpl implements AccountTerminateWritePlatformService {
|
||||
|
||||
private static final String PARAM_ACCOUNT_TYPE = "accountType";
|
||||
|
||||
private final SavingsAccountWritePlatformService savingsAccountWritePlatformService;
|
||||
private final CurrentAccountWritePlatformService currentAccountWritePlatformService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommandProcessingResult terminate(final Long accountId, final JsonCommand command) {
|
||||
final String accountType = command.stringValueOfParameterNamed(PARAM_ACCOUNT_TYPE);
|
||||
|
||||
if (accountType == null || accountType.isBlank()) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.account.terminate.accountType.required",
|
||||
"accountType is required: SAVINGS, CURRENT, FD or RD");
|
||||
}
|
||||
|
||||
return switch (accountType.toUpperCase()) {
|
||||
case "CURRENT" -> currentAccountWritePlatformService.terminateAccount(accountId, command);
|
||||
case "SAVINGS", "FD", "RD" -> savingsAccountWritePlatformService.terminate(accountId, command);
|
||||
default -> throw new PlatformDataIntegrityException(
|
||||
"error.msg.account.terminate.accountType.invalid",
|
||||
"Unknown accountType '" + accountType + "'. Expected SAVINGS, CURRENT, FD or RD.");
|
||||
};
|
||||
}
|
||||
}
|
||||
+206
-202
@@ -1,202 +1,206 @@
|
||||
/**
|
||||
* 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.account.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
|
||||
public final class AccountTransferEnumerations {
|
||||
|
||||
private AccountTransferEnumerations() {
|
||||
|
||||
}
|
||||
|
||||
public static EnumOptionData accountType(final Integer type) {
|
||||
return accountType(PortfolioAccountType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData accountType(final PortfolioAccountType type) {
|
||||
|
||||
EnumOptionData optionData = null;
|
||||
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.LOAN.getValue().longValue(), PortfolioAccountType.LOAN.getCode(),
|
||||
"Loan Account");
|
||||
break;
|
||||
case SAVINGS:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.SAVINGS.getValue().longValue(),
|
||||
PortfolioAccountType.SAVINGS.getCode(), "Savings Account");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData recurrenceType(final Integer type) {
|
||||
return recurrenceType(AccountTransferRecurrenceType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData recurrenceType(final AccountTransferRecurrenceType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case PERIODIC:
|
||||
optionData = new EnumOptionData(AccountTransferRecurrenceType.PERIODIC.getValue().longValue(),
|
||||
AccountTransferRecurrenceType.PERIODIC.getCode(), "Periodic Recurrence");
|
||||
break;
|
||||
case AS_PER_DUES:
|
||||
optionData = new EnumOptionData(AccountTransferRecurrenceType.AS_PER_DUES.getValue().longValue(),
|
||||
AccountTransferRecurrenceType.AS_PER_DUES.getCode(), "As Per Dues Recurrence");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData transferType(final Integer type) {
|
||||
return transferType(AccountTransferType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData transferType(final AccountTransferType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case ACCOUNT_TRANSFER:
|
||||
optionData = new EnumOptionData(AccountTransferType.ACCOUNT_TRANSFER.getValue().longValue(),
|
||||
AccountTransferType.ACCOUNT_TRANSFER.getCode(), "Account Transfer");
|
||||
break;
|
||||
case LOAN_REPAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.LOAN_REPAYMENT.getValue().longValue(),
|
||||
AccountTransferType.LOAN_REPAYMENT.getCode(), "Loan Repayment");
|
||||
break;
|
||||
case CHARGE_PAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.CHARGE_PAYMENT.getValue().longValue(),
|
||||
AccountTransferType.CHARGE_PAYMENT.getCode(), "Charge Payment");
|
||||
break;
|
||||
case INTEREST_TRANSFER:
|
||||
optionData = new EnumOptionData(AccountTransferType.INTEREST_TRANSFER.getValue().longValue(),
|
||||
AccountTransferType.INTEREST_TRANSFER.getCode(), "Interest Transfer");
|
||||
break;
|
||||
case LOAN_DOWN_PAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.LOAN_DOWN_PAYMENT.getValue().longValue(),
|
||||
AccountTransferType.LOAN_DOWN_PAYMENT.getCode(), "Loan Down Payment");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionPriority(final Integer type) {
|
||||
return standingInstructionPriority(StandingInstructionPriority.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionPriority(final StandingInstructionPriority type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case HIGH:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.HIGH.getValue().longValue(),
|
||||
StandingInstructionPriority.HIGH.getCode(), "High Priority");
|
||||
break;
|
||||
case LOW:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.LOW.getValue().longValue(),
|
||||
StandingInstructionPriority.LOW.getCode(), "Low Priority");
|
||||
break;
|
||||
case MEDIUM:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.MEDIUM.getValue().longValue(),
|
||||
StandingInstructionPriority.MEDIUM.getCode(), "Medium Priority");
|
||||
break;
|
||||
case URGENT:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.URGENT.getValue().longValue(),
|
||||
StandingInstructionPriority.URGENT.getCode(), "Urgent Priority");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionStatus(final Integer type) {
|
||||
return standingInstructionStatus(StandingInstructionStatus.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionStatus(final StandingInstructionStatus type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case ACTIVE:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.ACTIVE.getValue().longValue(),
|
||||
StandingInstructionStatus.ACTIVE.getCode(), "Active");
|
||||
break;
|
||||
case DELETED:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.DELETED.getValue().longValue(),
|
||||
StandingInstructionStatus.DELETED.getCode(), "Deleted");
|
||||
break;
|
||||
case DISABLED:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.DISABLED.getValue().longValue(),
|
||||
StandingInstructionStatus.DISABLED.getCode(), "Disabled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionType(final Integer type) {
|
||||
return standingInstructionType(StandingInstructionType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionType(final StandingInstructionType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case DUES:
|
||||
optionData = new EnumOptionData(StandingInstructionType.DUES.getValue().longValue(),
|
||||
StandingInstructionType.DUES.getCode(), "Dues");
|
||||
break;
|
||||
case FIXED:
|
||||
optionData = new EnumOptionData(StandingInstructionType.FIXED.getValue().longValue(),
|
||||
StandingInstructionType.FIXED.getCode(), "Fixed");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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.account.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRecurrenceType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionPriority;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||
|
||||
public final class AccountTransferEnumerations {
|
||||
|
||||
private AccountTransferEnumerations() {
|
||||
|
||||
}
|
||||
|
||||
public static EnumOptionData accountType(final Integer type) {
|
||||
return accountType(PortfolioAccountType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData accountType(final PortfolioAccountType type) {
|
||||
|
||||
EnumOptionData optionData = null;
|
||||
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.LOAN.getValue().longValue(), PortfolioAccountType.LOAN.getCode(),
|
||||
"Loan Account");
|
||||
break;
|
||||
case SAVINGS:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.SAVINGS.getValue().longValue(),
|
||||
PortfolioAccountType.SAVINGS.getCode(), "Savings Account");
|
||||
break;
|
||||
case CURRENT:
|
||||
optionData = new EnumOptionData(PortfolioAccountType.CURRENT.getValue().longValue(),
|
||||
PortfolioAccountType.CURRENT.getCode(), "Current Account");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData recurrenceType(final Integer type) {
|
||||
return recurrenceType(AccountTransferRecurrenceType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData recurrenceType(final AccountTransferRecurrenceType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case PERIODIC:
|
||||
optionData = new EnumOptionData(AccountTransferRecurrenceType.PERIODIC.getValue().longValue(),
|
||||
AccountTransferRecurrenceType.PERIODIC.getCode(), "Periodic Recurrence");
|
||||
break;
|
||||
case AS_PER_DUES:
|
||||
optionData = new EnumOptionData(AccountTransferRecurrenceType.AS_PER_DUES.getValue().longValue(),
|
||||
AccountTransferRecurrenceType.AS_PER_DUES.getCode(), "As Per Dues Recurrence");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData transferType(final Integer type) {
|
||||
return transferType(AccountTransferType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData transferType(final AccountTransferType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case ACCOUNT_TRANSFER:
|
||||
optionData = new EnumOptionData(AccountTransferType.ACCOUNT_TRANSFER.getValue().longValue(),
|
||||
AccountTransferType.ACCOUNT_TRANSFER.getCode(), "Account Transfer");
|
||||
break;
|
||||
case LOAN_REPAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.LOAN_REPAYMENT.getValue().longValue(),
|
||||
AccountTransferType.LOAN_REPAYMENT.getCode(), "Loan Repayment");
|
||||
break;
|
||||
case CHARGE_PAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.CHARGE_PAYMENT.getValue().longValue(),
|
||||
AccountTransferType.CHARGE_PAYMENT.getCode(), "Charge Payment");
|
||||
break;
|
||||
case INTEREST_TRANSFER:
|
||||
optionData = new EnumOptionData(AccountTransferType.INTEREST_TRANSFER.getValue().longValue(),
|
||||
AccountTransferType.INTEREST_TRANSFER.getCode(), "Interest Transfer");
|
||||
break;
|
||||
case LOAN_DOWN_PAYMENT:
|
||||
optionData = new EnumOptionData(AccountTransferType.LOAN_DOWN_PAYMENT.getValue().longValue(),
|
||||
AccountTransferType.LOAN_DOWN_PAYMENT.getCode(), "Loan Down Payment");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionPriority(final Integer type) {
|
||||
return standingInstructionPriority(StandingInstructionPriority.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionPriority(final StandingInstructionPriority type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case HIGH:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.HIGH.getValue().longValue(),
|
||||
StandingInstructionPriority.HIGH.getCode(), "High Priority");
|
||||
break;
|
||||
case LOW:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.LOW.getValue().longValue(),
|
||||
StandingInstructionPriority.LOW.getCode(), "Low Priority");
|
||||
break;
|
||||
case MEDIUM:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.MEDIUM.getValue().longValue(),
|
||||
StandingInstructionPriority.MEDIUM.getCode(), "Medium Priority");
|
||||
break;
|
||||
case URGENT:
|
||||
optionData = new EnumOptionData(StandingInstructionPriority.URGENT.getValue().longValue(),
|
||||
StandingInstructionPriority.URGENT.getCode(), "Urgent Priority");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionStatus(final Integer type) {
|
||||
return standingInstructionStatus(StandingInstructionStatus.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionStatus(final StandingInstructionStatus type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case ACTIVE:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.ACTIVE.getValue().longValue(),
|
||||
StandingInstructionStatus.ACTIVE.getCode(), "Active");
|
||||
break;
|
||||
case DELETED:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.DELETED.getValue().longValue(),
|
||||
StandingInstructionStatus.DELETED.getCode(), "Deleted");
|
||||
break;
|
||||
case DISABLED:
|
||||
optionData = new EnumOptionData(StandingInstructionStatus.DISABLED.getValue().longValue(),
|
||||
StandingInstructionStatus.DISABLED.getCode(), "Disabled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionType(final Integer type) {
|
||||
return standingInstructionType(StandingInstructionType.fromInt(type));
|
||||
}
|
||||
|
||||
public static EnumOptionData standingInstructionType(final StandingInstructionType type) {
|
||||
EnumOptionData optionData = null;
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case INVALID:
|
||||
break;
|
||||
case DUES:
|
||||
optionData = new EnumOptionData(StandingInstructionType.DUES.getValue().longValue(),
|
||||
StandingInstructionType.DUES.getCode(), "Dues");
|
||||
break;
|
||||
case FIXED:
|
||||
optionData = new EnumOptionData(StandingInstructionType.FIXED.getValue().longValue(),
|
||||
StandingInstructionType.FIXED.getCode(), "Fixed");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+865
-561
File diff suppressed because it is too large
Load Diff
+482
-396
@@ -1,396 +1,482 @@
|
||||
/**
|
||||
* 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.account.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
|
||||
import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
|
||||
import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccountReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
// mapper
|
||||
private final PortfolioSavingsAccountMapper savingsAccountMapper;
|
||||
private final PortfolioLoanAccountMapper loanAccountMapper;
|
||||
private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper;
|
||||
|
||||
public PortfolioAccountReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
|
||||
this.loanAccountMapper = new PortfolioLoanAccountMapper();
|
||||
this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper(sqlGenerator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId) {
|
||||
return retrieveOne(accountId, accountTypeId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId, final String currencyCode) {
|
||||
|
||||
Object[] sqlParams = new Object[] { accountId };
|
||||
PortfolioAccountData accountData = null;
|
||||
try {
|
||||
String sql = null;
|
||||
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
|
||||
switch (accountType) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
|
||||
sql = "select " + this.loanAccountMapper.schema() + " where la.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and la.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.loanAccountMapper, sqlParams);
|
||||
break;
|
||||
case SAVINGS:
|
||||
sql = "select " + this.savingsAccountMapper.schema() + " where sa.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and sa.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams);
|
||||
break;
|
||||
}
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountTransferNotFoundException(accountId, e);
|
||||
}
|
||||
|
||||
return accountData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PortfolioAccountData> retrieveAllForLookup(final PortfolioAccountDTO portfolioAccountDTO) {
|
||||
|
||||
final List<Object> sqlParams = new ArrayList<>();
|
||||
// sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
Collection<PortfolioAccountData> accounts = null;
|
||||
String sql = null;
|
||||
long defaultAccountStatus = 300; // Active Status
|
||||
if (portfolioAccountDTO.getAccountStatus() != null) {
|
||||
defaultAccountStatus = portfolioAccountDTO.getFirstAccountStatus();
|
||||
}
|
||||
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(portfolioAccountDTO.getAccountTypeId());
|
||||
switch (accountType) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
sql = "select " + this.loanAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " la.client_id = ? and la.loan_status_id in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " la.loan_status_id in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and la.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.loanAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
case SAVINGS:
|
||||
sql = "select " + this.savingsAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " sa.client_id = ? and sa.status_enum in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " sa.status_enum in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and sa.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.getDepositType() != null) {
|
||||
sql += " and sa.deposit_type_enum = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getDepositType().shortValue());
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.isExcludeOverDraftAccounts()) {
|
||||
sql += " and sa.allow_overdraft = false";
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.getClientId() == null && portfolioAccountDTO.getGroupId() != null) {
|
||||
sql += " and sa.group_id = ? ";
|
||||
sqlParams.add(portfolioAccountDTO.getGroupId());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
private static final class PortfolioSavingsAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioSavingsAccountMapper() {
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("sp.id as productId, sp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("sa.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_savings_account sa ");
|
||||
sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortfolioLoanAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioLoanAccountMapper() {
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("la.total_overpaid_derived as totalOverpaid, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_loan la ");
|
||||
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortfolioLoanAccountRefundByTransferMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private String schemaSql;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
|
||||
PortfolioLoanAccountRefundByTransferMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
this.sqlGenerator = sqlGenerator;
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
final StringBuilder amountQueryString = new StringBuilder(400);
|
||||
amountQueryString.append("(select (SUM(COALESCE(mr.principal_completed_derived, 0)) +");
|
||||
amountQueryString.append("SUM(COALESCE(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived");
|
||||
amountQueryString.append(" from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id");
|
||||
amountQueryString.append(" where ml.id=? and ml.loan_status_id = 300");
|
||||
amountQueryString.append(" and mr.duedate >= " + sqlGenerator.currentBusinessDate() + " group by ml.id having");
|
||||
amountQueryString.append(" (SUM(COALESCE(mr.principal_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(COALESCE(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) > 0) as totalOverpaid ");
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append(amountQueryString.toString());
|
||||
sqlBuilder.append(", ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_loan la ");
|
||||
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId) {
|
||||
// TODO Auto-generated method stub
|
||||
Object[] sqlParams = new Object[] { accountId, accountId };
|
||||
PortfolioAccountData accountData = null;
|
||||
// String currencyCode = null;
|
||||
try {
|
||||
String sql = null;
|
||||
// final PortfolioAccountType accountType =
|
||||
// PortfolioAccountType.fromInt(accountTypeId);
|
||||
|
||||
sql = "select " + this.accountRefundByTransferMapper.schema() + " where la.id = ?";
|
||||
/*
|
||||
* if (currencyCode != null) { sql += " and la.currency_code = ?"; sqlParams = new Object[] {accountId ,
|
||||
* accountId,currencyCode }; }
|
||||
*/
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.accountRefundByTransferMapper, sqlParams);
|
||||
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountTransferNotFoundException(accountId, e);
|
||||
}
|
||||
|
||||
return accountData;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.data.PortfolioAccountDTO;
|
||||
import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
|
||||
import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccountReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
// mapper
|
||||
private final PortfolioSavingsAccountMapper savingsAccountMapper;
|
||||
private final PortfolioLoanAccountMapper loanAccountMapper;
|
||||
private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper;
|
||||
private final PortfolioCurrentAccountMapper currentAccountMapper;
|
||||
|
||||
public PortfolioAccountReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
|
||||
this.loanAccountMapper = new PortfolioLoanAccountMapper();
|
||||
this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper(sqlGenerator);
|
||||
this.currentAccountMapper = new PortfolioCurrentAccountMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId) {
|
||||
return retrieveOne(accountId, accountTypeId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOne(final Long accountId, final Integer accountTypeId, final String currencyCode) {
|
||||
|
||||
Object[] sqlParams = new Object[] { accountId };
|
||||
PortfolioAccountData accountData = null;
|
||||
try {
|
||||
String sql = null;
|
||||
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(accountTypeId);
|
||||
switch (accountType) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
|
||||
sql = "select " + this.loanAccountMapper.schema() + " where la.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and la.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.loanAccountMapper, sqlParams);
|
||||
break;
|
||||
case SAVINGS:
|
||||
sql = "select " + this.savingsAccountMapper.schema() + " where sa.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and sa.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams);
|
||||
break;
|
||||
case CURRENT:
|
||||
sql = "select " + this.currentAccountMapper.schema() + " where ca.id = ?";
|
||||
if (currencyCode != null) {
|
||||
sql += " and ca.currency_code = ?";
|
||||
sqlParams = new Object[] { accountId, currencyCode };
|
||||
}
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.currentAccountMapper, sqlParams);
|
||||
break;
|
||||
}
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountTransferNotFoundException(accountId, e);
|
||||
}
|
||||
|
||||
return accountData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PortfolioAccountData> retrieveAllForLookup(final PortfolioAccountDTO portfolioAccountDTO) {
|
||||
|
||||
final List<Object> sqlParams = new ArrayList<>();
|
||||
// sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
Collection<PortfolioAccountData> accounts = null;
|
||||
String sql = null;
|
||||
long defaultAccountStatus = 300; // Active Status
|
||||
if (portfolioAccountDTO.getAccountStatus() != null) {
|
||||
defaultAccountStatus = portfolioAccountDTO.getFirstAccountStatus();
|
||||
}
|
||||
final PortfolioAccountType accountType = PortfolioAccountType.fromInt(portfolioAccountDTO.getAccountTypeId());
|
||||
switch (accountType) {
|
||||
case INVALID:
|
||||
break;
|
||||
case LOAN:
|
||||
sql = "select " + this.loanAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " la.client_id = ? and la.loan_status_id in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " la.loan_status_id in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and la.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.loanAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
case SAVINGS:
|
||||
sql = "select " + this.savingsAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " sa.client_id = ? and sa.status_enum in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " sa.status_enum in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and sa.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.getDepositType() != null) {
|
||||
sql += " and sa.deposit_type_enum = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getDepositType().shortValue());
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.isExcludeOverDraftAccounts()) {
|
||||
sql += " and sa.allow_overdraft = false";
|
||||
}
|
||||
|
||||
if (portfolioAccountDTO.getClientId() == null && portfolioAccountDTO.getGroupId() != null) {
|
||||
sql += " and sa.group_id = ? ";
|
||||
sqlParams.add(portfolioAccountDTO.getGroupId());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
case CURRENT:
|
||||
sql = "select " + this.currentAccountMapper.schema() + " where ";
|
||||
if (portfolioAccountDTO.getClientId() != null) {
|
||||
sql += " ca.client_id = ? and ca.status_enum in (?) ";
|
||||
sqlParams.add(portfolioAccountDTO.getClientId());
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
} else {
|
||||
sql += " ca.status_enum in (?) ";
|
||||
sqlParams.add(defaultAccountStatus);
|
||||
}
|
||||
if (portfolioAccountDTO.getCurrencyCode() != null) {
|
||||
sql += " and ca.currency_code = ?";
|
||||
sqlParams.add(portfolioAccountDTO.getCurrencyCode());
|
||||
}
|
||||
|
||||
accounts = this.jdbcTemplate.query(sql, this.currentAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||
break;
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
private static final class PortfolioSavingsAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioSavingsAccountMapper() {
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("sp.id as productId, sp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("sa.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_savings_account sa ");
|
||||
sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = sa.field_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortfolioLoanAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioLoanAccountMapper() {
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("la.total_overpaid_derived as totalOverpaid, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_loan la ");
|
||||
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PortfolioLoanAccountRefundByTransferMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private String schemaSql;
|
||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||
|
||||
PortfolioLoanAccountRefundByTransferMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
this.sqlGenerator = sqlGenerator;
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
final StringBuilder amountQueryString = new StringBuilder(400);
|
||||
amountQueryString.append("(select (SUM(COALESCE(mr.principal_completed_derived, 0)) +");
|
||||
amountQueryString.append("SUM(COALESCE(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) as total_in_advance_derived");
|
||||
amountQueryString.append(" from m_loan ml INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id");
|
||||
amountQueryString.append(" where ml.id=? and ml.loan_status_id = 300");
|
||||
amountQueryString.append(" and mr.duedate >= " + sqlGenerator.currentBusinessDate() + " group by ml.id having");
|
||||
amountQueryString.append(" (SUM(COALESCE(mr.principal_completed_derived, 0)) + ");
|
||||
amountQueryString.append(" SUM(COALESCE(mr.interest_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.fee_charges_completed_derived, 0)) + ");
|
||||
amountQueryString.append("SUM(COALESCE(mr.penalty_charges_completed_derived, 0))) > 0) as totalOverpaid ");
|
||||
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("la.id as id, la.account_no as accountNo, la.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("g.id as groupId, g.display_name as groupName, ");
|
||||
sqlBuilder.append("lp.id as productId, lp.name as productName, ");
|
||||
sqlBuilder.append("s.id as fieldOfficerId, s.display_name as fieldOfficerName, ");
|
||||
sqlBuilder.append("la.currency_code as currencyCode, la.currency_digits as currencyDigits,");
|
||||
sqlBuilder.append("la.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append(amountQueryString.toString());
|
||||
sqlBuilder.append(", ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_loan la ");
|
||||
sqlBuilder.append("join m_product_loan lp ON la.product_id = lp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = la.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = la.client_id ");
|
||||
sqlBuilder.append("left join m_group g ON g.id = la.group_id ");
|
||||
sqlBuilder.append("left join m_staff s ON s.id = la.loan_officer_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = rs.getLong("fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMulitplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final BigDecimal amtForTransfer = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverpaid");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMulitplesOf, currencyDisplaySymbol,
|
||||
currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency, amtForTransfer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retrieveOneByPaidInAdvance(Long accountId, Integer accountTypeId) {
|
||||
// TODO Auto-generated method stub
|
||||
Object[] sqlParams = new Object[] { accountId, accountId };
|
||||
PortfolioAccountData accountData = null;
|
||||
// String currencyCode = null;
|
||||
try {
|
||||
String sql = null;
|
||||
// final PortfolioAccountType accountType =
|
||||
// PortfolioAccountType.fromInt(accountTypeId);
|
||||
|
||||
sql = "select " + this.accountRefundByTransferMapper.schema() + " where la.id = ?";
|
||||
/*
|
||||
* if (currencyCode != null) { sql += " and la.currency_code = ?"; sqlParams = new Object[] {accountId ,
|
||||
* accountId,currencyCode }; }
|
||||
*/
|
||||
|
||||
accountData = this.jdbcTemplate.queryForObject(sql, this.accountRefundByTransferMapper, sqlParams);
|
||||
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AccountTransferNotFoundException(accountId, e);
|
||||
}
|
||||
|
||||
return accountData;
|
||||
}
|
||||
|
||||
private static final class PortfolioCurrentAccountMapper implements RowMapper<PortfolioAccountData> {
|
||||
|
||||
private final String schemaSql;
|
||||
|
||||
PortfolioCurrentAccountMapper() {
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("ca.id as id, ca.account_no as accountNo, ca.external_id as externalId, ");
|
||||
sqlBuilder.append("c.id as clientId, c.display_name as clientName, ");
|
||||
sqlBuilder.append("null as groupId, null as groupName, ");
|
||||
sqlBuilder.append("cp.id as productId, cp.name as productName, ");
|
||||
sqlBuilder.append("null as fieldOfficerId, null as fieldOfficerName, ");
|
||||
sqlBuilder.append("ca.currency_code as currencyCode, ca.currency_digits as currencyDigits, ");
|
||||
sqlBuilder.append("ca.currency_multiplesof as inMultiplesOf, ");
|
||||
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol ");
|
||||
sqlBuilder.append("from m_current_account ca ");
|
||||
sqlBuilder.append("join m_current_product cp ON ca.product_id = cp.id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = ca.currency_code ");
|
||||
sqlBuilder.append("left join m_client c ON c.id = ca.client_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
return this.schemaSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
|
||||
final Long groupId = JdbcSupport.getLong(rs, "groupId");
|
||||
final String groupName = rs.getString("groupName");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
final String clientName = rs.getString("clientName");
|
||||
|
||||
final Long productId = rs.getLong("productId");
|
||||
final String productName = rs.getString("productName");
|
||||
|
||||
final Long fieldOfficerId = JdbcSupport.getLong(rs, "fieldOfficerId");
|
||||
final String fieldOfficerName = rs.getString("fieldOfficerName");
|
||||
|
||||
final String currencyCode = rs.getString("currencyCode");
|
||||
final String currencyName = rs.getString("currencyName");
|
||||
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
|
||||
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
|
||||
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
|
||||
currencyDisplaySymbol, currencyNameCode);
|
||||
|
||||
return new PortfolioAccountData(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
|
||||
fieldOfficerId, fieldOfficerName, currency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+626
-591
File diff suppressed because it is too large
Load Diff
+179
-145
@@ -1,145 +1,179 @@
|
||||
/**
|
||||
* 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.account.service;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.exception.StandingInstructionNotFoundException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class StandingInstructionWritePlatformServiceImpl implements StandingInstructionWritePlatformService {
|
||||
|
||||
private final StandingInstructionDataValidator standingInstructionDataValidator;
|
||||
private final StandingInstructionAssembler standingInstructionAssembler;
|
||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
private final StandingInstructionRepository standingInstructionRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult create(final JsonCommand command) {
|
||||
|
||||
this.standingInstructionDataValidator.validateForCreate(command);
|
||||
|
||||
final Integer fromAccountTypeId = command.integerValueSansLocaleOfParameterNamed(fromAccountTypeParamName);
|
||||
final PortfolioAccountType fromAccountType = PortfolioAccountType.fromInt(fromAccountTypeId);
|
||||
|
||||
final Integer toAccountTypeId = command.integerValueSansLocaleOfParameterNamed(toAccountTypeParamName);
|
||||
final PortfolioAccountType toAccountType = PortfolioAccountType.fromInt(toAccountTypeId);
|
||||
|
||||
final Long fromClientId = command.longValueOfParameterNamed(fromClientIdParamName);
|
||||
|
||||
Long standingInstructionId = null;
|
||||
try {
|
||||
if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler
|
||||
.assembleSavingsToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleSavingsToLoanTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleLoanToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
|
||||
}
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
final Throwable throwable = dve.getMostSpecificCause();
|
||||
handleDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(standingInstructionId)
|
||||
.withClientId(fromClientId);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void handleDataIntegrityIssues(final JsonCommand command, Throwable realCause, final NonTransientDataAccessException dve) {
|
||||
if (realCause.getMessage().contains("name")) {
|
||||
final String name = command.stringValueOfParameterNamed(StandingInstructionApiConstants.nameParamName);
|
||||
throw new PlatformDataIntegrityException("error.msg.standinginstruction.duplicate.name",
|
||||
"Standinginstruction with name `" + name + "` already exists", "name", name);
|
||||
}
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.client.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||
}
|
||||
|
||||
private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult update(final Long id, final JsonCommand command) {
|
||||
this.standingInstructionDataValidator.validateForUpdate(command);
|
||||
AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findById(id)
|
||||
.orElseThrow(() -> new StandingInstructionNotFoundException(id));
|
||||
final Map<String, Object> actualChanges = standingInstructionsForUpdate.update(command);
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(id).with(actualChanges).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult delete(final Long id) {
|
||||
AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findById(id).orElseThrow();
|
||||
// update the "deleted" and "name" properties of the standing
|
||||
// instruction
|
||||
standingInstructionsForUpdate.delete();
|
||||
|
||||
final Map<String, Object> actualChanges = new HashMap<>();
|
||||
actualChanges.put(statusParamName, StandingInstructionStatus.DELETED.getValue());
|
||||
return new CommandProcessingResultBuilder().withEntityId(id).with(actualChanges).build();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.service;
|
||||
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.fromClientIdParamName;
|
||||
import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAccountTypeParamName;
|
||||
import static org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants.statusParamName;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.api.StandingInstructionApiConstants;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstruction;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||
import org.apache.fineract.portfolio.account.exception.StandingInstructionNotFoundException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class StandingInstructionWritePlatformServiceImpl implements StandingInstructionWritePlatformService {
|
||||
|
||||
private final StandingInstructionDataValidator standingInstructionDataValidator;
|
||||
private final StandingInstructionAssembler standingInstructionAssembler;
|
||||
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||
private final StandingInstructionRepository standingInstructionRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult create(final JsonCommand command) {
|
||||
|
||||
this.standingInstructionDataValidator.validateForCreate(command);
|
||||
|
||||
final Integer fromAccountTypeId = command.integerValueSansLocaleOfParameterNamed(fromAccountTypeParamName);
|
||||
final PortfolioAccountType fromAccountType = PortfolioAccountType.fromInt(fromAccountTypeId);
|
||||
|
||||
final Integer toAccountTypeId = command.integerValueSansLocaleOfParameterNamed(toAccountTypeParamName);
|
||||
final PortfolioAccountType toAccountType = PortfolioAccountType.fromInt(toAccountTypeId);
|
||||
|
||||
final Long fromClientId = command.longValueOfParameterNamed(fromClientIdParamName);
|
||||
|
||||
Long standingInstructionId = null;
|
||||
try {
|
||||
if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler
|
||||
.assembleSavingsToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleSavingsToLoanTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleLoanToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isCurrentToCurrentAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleCurrentToCurrentTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isCurrentToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleCurrentToSavingsTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isSavingsToCurrentAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleSavingsToCurrentTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
} else if (isCurrentToLoanAccountTransfer(fromAccountType, toAccountType)) {
|
||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleCurrentToLoanTransfer(command);
|
||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||
}
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
final Throwable throwable = dve.getMostSpecificCause();
|
||||
handleDataIntegrityIssues(command, throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(standingInstructionId)
|
||||
.withClientId(fromClientId);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void handleDataIntegrityIssues(final JsonCommand command, Throwable realCause, final NonTransientDataAccessException dve) {
|
||||
if (realCause.getMessage().contains("name")) {
|
||||
final String name = command.stringValueOfParameterNamed(StandingInstructionApiConstants.nameParamName);
|
||||
throw new PlatformDataIntegrityException("error.msg.standinginstruction.duplicate.name",
|
||||
"Standinginstruction with name `" + name + "` already exists", "name", name);
|
||||
}
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.client.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||
}
|
||||
|
||||
private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
private boolean isCurrentToCurrentAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isCurrentAccount() && toAccountType.isCurrentAccount();
|
||||
}
|
||||
|
||||
private boolean isCurrentToSavingsAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isCurrentAccount() && toAccountType.isSavingsAccount();
|
||||
}
|
||||
|
||||
private boolean isSavingsToCurrentAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isSavingsAccount() && toAccountType.isCurrentAccount();
|
||||
}
|
||||
|
||||
private boolean isCurrentToLoanAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||
final PortfolioAccountType toAccountType) {
|
||||
return fromAccountType.isCurrentAccount() && toAccountType.isLoanAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult update(final Long id, final JsonCommand command) {
|
||||
this.standingInstructionDataValidator.validateForUpdate(command);
|
||||
AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findById(id)
|
||||
.orElseThrow(() -> new StandingInstructionNotFoundException(id));
|
||||
final Map<String, Object> actualChanges = standingInstructionsForUpdate.update(command);
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(id).with(actualChanges).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult delete(final Long id) {
|
||||
AccountTransferStandingInstruction standingInstructionsForUpdate = this.standingInstructionRepository.findById(id).orElseThrow();
|
||||
// update the "deleted" and "name" properties of the standing
|
||||
// instruction
|
||||
standingInstructionsForUpdate.delete();
|
||||
|
||||
final Map<String, Object> actualChanges = new HashMap<>();
|
||||
actualChanges.put(statusParamName, StandingInstructionStatus.DELETED.getValue());
|
||||
return new CommandProcessingResultBuilder().withEntityId(id).with(actualChanges).build();
|
||||
}
|
||||
}
|
||||
|
||||
+140
-134
@@ -1,134 +1,140 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.account.starter;
|
||||
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
|
||||
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||
import org.apache.fineract.portfolio.account.mapper.AccountTransfersMapper;
|
||||
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
|
||||
import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.domain.GSIMRepositoy;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class AccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountAssociationsReadPlatformService.class)
|
||||
public AccountAssociationsReadPlatformService accountAssociationsReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new AccountAssociationsReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountTransfersReadPlatformService.class)
|
||||
public AccountTransfersReadPlatformService accountTransfersReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService,
|
||||
PortfolioAccountReadPlatformService portfolioAccountReadPlatformService, ColumnValidator columnValidator,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, AccountTransfersMapper accountTransfersMapper, PaginationHelper paginationHelper,
|
||||
SqlValidator sqlValidator) {
|
||||
return new AccountTransfersReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService,
|
||||
portfolioAccountReadPlatformService, columnValidator, sqlGenerator, accountTransfersMapper, paginationHelper, sqlValidator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountTransfersWritePlatformService.class)
|
||||
public AccountTransfersWritePlatformService accountTransfersWritePlatformService(
|
||||
AccountTransfersDataValidator accountTransfersDataValidator, AccountTransferAssembler accountTransferAssembler,
|
||||
AccountTransferRepository accountTransferRepository, SavingsAccountAssembler savingsAccountAssembler,
|
||||
SavingsAccountDomainService savingsAccountDomainService, LoanAssembler loanAccountAssembler,
|
||||
LoanAccountDomainService loanAccountDomainService, SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||
AccountTransferDetailRepository accountTransferDetailRepository, LoanReadPlatformService loanReadPlatformService,
|
||||
GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory,
|
||||
FineractProperties fineractProperties) {
|
||||
return new AccountTransfersWritePlatformServiceImpl(accountTransfersDataValidator, accountTransferAssembler,
|
||||
accountTransferRepository, savingsAccountAssembler, savingsAccountDomainService, loanAccountAssembler,
|
||||
loanAccountDomainService, savingsAccountWritePlatformService, accountTransferDetailRepository, loanReadPlatformService,
|
||||
gsimRepository, configurationDomainService, externalIdFactory, fineractProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PortfolioAccountReadPlatformService.class)
|
||||
public PortfolioAccountReadPlatformService portfolioAccountReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
return new PortfolioAccountReadPlatformServiceImpl(jdbcTemplate, sqlGenerator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionHistoryReadPlatformService.class)
|
||||
public StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
|
||||
return new StandingInstructionHistoryReadPlatformServiceImpl(jdbcTemplate, columnValidator, sqlGenerator, paginationHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionReadPlatformService.class)
|
||||
public StandingInstructionReadPlatformService standingInstructionReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService,
|
||||
PortfolioAccountReadPlatformService portfolioAccountReadPlatformService,
|
||||
DropdownReadPlatformService dropdownReadPlatformService, ColumnValidator columnValidator,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
|
||||
return new StandingInstructionReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService,
|
||||
portfolioAccountReadPlatformService, dropdownReadPlatformService, columnValidator, sqlGenerator, paginationHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionWritePlatformService.class)
|
||||
public StandingInstructionWritePlatformService standingInstructionWritePlatformService(
|
||||
StandingInstructionDataValidator standingInstructionDataValidator, StandingInstructionAssembler standingInstructionAssembler,
|
||||
AccountTransferDetailRepository accountTransferDetailRepository, StandingInstructionRepository standingInstructionRepository) {
|
||||
return new StandingInstructionWritePlatformServiceImpl(standingInstructionDataValidator, standingInstructionAssembler,
|
||||
accountTransferDetailRepository, standingInstructionRepository);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.account.starter;
|
||||
|
||||
import org.apache.fineract.accounting.journalentry.service.AccountingProcessorHelper;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
|
||||
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator;
|
||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionAssembler;
|
||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
|
||||
import org.apache.fineract.portfolio.account.mapper.AccountTransfersMapper;
|
||||
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.PortfolioAccountReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionHistoryReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformService;
|
||||
import org.apache.fineract.portfolio.account.service.StandingInstructionWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
|
||||
import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.domain.GSIMRepositoy;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class AccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountAssociationsReadPlatformService.class)
|
||||
public AccountAssociationsReadPlatformService accountAssociationsReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new AccountAssociationsReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountTransfersReadPlatformService.class)
|
||||
public AccountTransfersReadPlatformService accountTransfersReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService,
|
||||
PortfolioAccountReadPlatformService portfolioAccountReadPlatformService, ColumnValidator columnValidator,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, AccountTransfersMapper accountTransfersMapper, PaginationHelper paginationHelper,
|
||||
SqlValidator sqlValidator) {
|
||||
return new AccountTransfersReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService,
|
||||
portfolioAccountReadPlatformService, columnValidator, sqlGenerator, accountTransfersMapper, paginationHelper, sqlValidator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccountTransfersWritePlatformService.class)
|
||||
public AccountTransfersWritePlatformService accountTransfersWritePlatformService(
|
||||
AccountTransfersDataValidator accountTransfersDataValidator, AccountTransferAssembler accountTransferAssembler,
|
||||
AccountTransferRepository accountTransferRepository, SavingsAccountAssembler savingsAccountAssembler,
|
||||
SavingsAccountDomainService savingsAccountDomainService, LoanAssembler loanAccountAssembler,
|
||||
LoanAccountDomainService loanAccountDomainService, SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||
AccountTransferDetailRepository accountTransferDetailRepository, LoanReadPlatformService loanReadPlatformService,
|
||||
GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory,
|
||||
FineractProperties fineractProperties, CurrentAccountRepository currentAccountRepository,
|
||||
CurrentAccountTransactionRepository currentAccountTransactionRepository,
|
||||
AccountingProcessorHelper accountingProcessorHelper) {
|
||||
return new AccountTransfersWritePlatformServiceImpl(accountTransfersDataValidator, accountTransferAssembler,
|
||||
accountTransferRepository, savingsAccountAssembler, savingsAccountDomainService, loanAccountAssembler,
|
||||
loanAccountDomainService, savingsAccountWritePlatformService, accountTransferDetailRepository, loanReadPlatformService,
|
||||
gsimRepository, configurationDomainService, externalIdFactory, fineractProperties,
|
||||
currentAccountRepository, currentAccountTransactionRepository, accountingProcessorHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PortfolioAccountReadPlatformService.class)
|
||||
public PortfolioAccountReadPlatformService portfolioAccountReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||
return new PortfolioAccountReadPlatformServiceImpl(jdbcTemplate, sqlGenerator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionHistoryReadPlatformService.class)
|
||||
public StandingInstructionHistoryReadPlatformService standingInstructionHistoryReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
|
||||
return new StandingInstructionHistoryReadPlatformServiceImpl(jdbcTemplate, columnValidator, sqlGenerator, paginationHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionReadPlatformService.class)
|
||||
public StandingInstructionReadPlatformService standingInstructionReadPlatformService(JdbcTemplate jdbcTemplate,
|
||||
ClientReadPlatformService clientReadPlatformService, OfficeReadPlatformService officeReadPlatformService,
|
||||
PortfolioAccountReadPlatformService portfolioAccountReadPlatformService,
|
||||
DropdownReadPlatformService dropdownReadPlatformService, ColumnValidator columnValidator,
|
||||
DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
|
||||
return new StandingInstructionReadPlatformServiceImpl(jdbcTemplate, clientReadPlatformService, officeReadPlatformService,
|
||||
portfolioAccountReadPlatformService, dropdownReadPlatformService, columnValidator, sqlGenerator, paginationHelper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StandingInstructionWritePlatformService.class)
|
||||
public StandingInstructionWritePlatformService standingInstructionWritePlatformService(
|
||||
StandingInstructionDataValidator standingInstructionDataValidator, StandingInstructionAssembler standingInstructionAssembler,
|
||||
AccountTransferDetailRepository accountTransferDetailRepository, StandingInstructionRepository standingInstructionRepository) {
|
||||
return new StandingInstructionWritePlatformServiceImpl(standingInstructionDataValidator, standingInstructionAssembler,
|
||||
accountTransferDetailRepository, standingInstructionRepository);
|
||||
}
|
||||
}
|
||||
|
||||
+30
-1
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.accountdetails.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
|
||||
|
||||
@@ -44,12 +45,28 @@ public class GuarantorAccountSummaryData {
|
||||
private final Boolean isActive;
|
||||
private final String relationship;
|
||||
private final BigDecimal onHoldAmount;
|
||||
private final String savingsAccountNo;
|
||||
private final BigDecimal amountReleased;
|
||||
private final String borrowerName;
|
||||
private final BigDecimal totalOverdue;
|
||||
private final LocalDate overdueSinceDate;
|
||||
private final LocalDate submittedOnDate;
|
||||
private final LocalDate approvedOnDate;
|
||||
private final LocalDate disbursedOnDate;
|
||||
private final LocalDate expectedMaturityDate;
|
||||
private final BigDecimal amountRemaining;
|
||||
private final BigDecimal savingsBalance;
|
||||
private final Integer missedInstallments;
|
||||
|
||||
public GuarantorAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
|
||||
final String loanProductName, final String shortLoanProductName, final LoanStatusEnumData loanStatus,
|
||||
final EnumOptionData loanType, final Integer loanCycle, final Boolean inArrears, final BigDecimal originalLoan,
|
||||
final BigDecimal loanBalance, final BigDecimal amountPaid, final Boolean isActive, final String relationship,
|
||||
final BigDecimal onHoldAmount) {
|
||||
final BigDecimal onHoldAmount, final String savingsAccountNo, final BigDecimal amountReleased,
|
||||
final String borrowerName, final BigDecimal totalOverdue, final LocalDate overdueSinceDate,
|
||||
final LocalDate submittedOnDate, final LocalDate approvedOnDate, final LocalDate disbursedOnDate,
|
||||
final LocalDate expectedMaturityDate, final BigDecimal amountRemaining, final BigDecimal savingsBalance,
|
||||
final Integer missedInstallments) {
|
||||
this.id = id;
|
||||
this.accountNo = accountNo;
|
||||
this.externalId = externalId;
|
||||
@@ -66,5 +83,17 @@ public class GuarantorAccountSummaryData {
|
||||
this.isActive = isActive;
|
||||
this.relationship = relationship;
|
||||
this.onHoldAmount = onHoldAmount;
|
||||
this.savingsAccountNo = savingsAccountNo;
|
||||
this.amountReleased = amountReleased;
|
||||
this.borrowerName = borrowerName;
|
||||
this.totalOverdue = totalOverdue;
|
||||
this.overdueSinceDate = overdueSinceDate;
|
||||
this.submittedOnDate = submittedOnDate;
|
||||
this.approvedOnDate = approvedOnDate;
|
||||
this.disbursedOnDate = disbursedOnDate;
|
||||
this.expectedMaturityDate = expectedMaturityDate;
|
||||
this.amountRemaining = amountRemaining;
|
||||
this.savingsBalance = savingsBalance;
|
||||
this.missedInstallments = missedInstallments;
|
||||
}
|
||||
}
|
||||
|
||||
+812
-785
File diff suppressed because it is too large
Load Diff
+298
@@ -0,0 +1,298 @@
|
||||
package org.apache.fineract.portfolio.cashexchange.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashreport.service.CashierRoleHelper;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchange;
|
||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeDetail;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeDetailRepository;
|
||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeRepository;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Path("/v1/cashexchange")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public class CashExchangeApiResource {
|
||||
|
||||
private final CashExchangeRepository repository;
|
||||
private final CashExchangeDetailRepository detailRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
||||
private final CashierRoleHelper cashierRoleHelper;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
@Transactional
|
||||
public String create(String body) {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
|
||||
final Long accountId = json.has("accountId") && !json.get("accountId").isJsonNull() ? json.get("accountId").getAsLong() : null;
|
||||
final String accountNo = json.has("accountNo") && !json.get("accountNo").isJsonNull() ? json.get("accountNo").getAsString()
|
||||
: null;
|
||||
final Long clientId = json.has("clientId") && !json.get("clientId").isJsonNull() ? json.get("clientId").getAsLong() : null;
|
||||
final LocalDate exchangeDate = json.has("exchangeDate") && !json.get("exchangeDate").isJsonNull()
|
||||
? LocalDate.parse(json.get("exchangeDate").getAsString())
|
||||
: null;
|
||||
final String note = json.has("note") && !json.get("note").isJsonNull() ? json.get("note").getAsString() : null;
|
||||
final String customerIdProof = json.has("customerIdProof") && !json.get("customerIdProof").isJsonNull()
|
||||
? json.get("customerIdProof").getAsString() : null;
|
||||
|
||||
final JsonArray received = json.has("received") && json.get("received").isJsonArray() ? json.getAsJsonArray("received")
|
||||
: new JsonArray();
|
||||
final JsonArray given = json.has("given") && json.get("given").isJsonArray() ? json.getAsJsonArray("given") : new JsonArray();
|
||||
|
||||
final BigDecimal receivedTotal = sumTotal(received);
|
||||
final BigDecimal givenTotal = sumTotal(given);
|
||||
|
||||
if (!hasPositiveCount(received) || !hasPositiveCount(given)) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.empty",
|
||||
"Both received and given denominations are required.");
|
||||
}
|
||||
if (receivedTotal.compareTo(givenTotal) != 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.mismatch",
|
||||
"Received total (" + receivedTotal + ") must equal given total (" + givenTotal + ").");
|
||||
}
|
||||
if (receivedTotal.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.empty",
|
||||
"Both received and given denominations are required.");
|
||||
}
|
||||
|
||||
CashExchange header = new CashExchange();
|
||||
header.setAccountId(accountId);
|
||||
header.setAccountNo(accountNo);
|
||||
header.setClientId(clientId);
|
||||
header.setExchangeAmount(receivedTotal);
|
||||
header.setExchangeDate(exchangeDate);
|
||||
header.setNote(note);
|
||||
header.setCustomerIdProof(customerIdProof);
|
||||
header.setIsReversed(false);
|
||||
header.setCreatedAt(LocalDateTime.now());
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
if (user != null) {
|
||||
header.setPerformedBy(user.getId());
|
||||
header.setPerformedByUsername(user.getUsername());
|
||||
// Auto-detect cashier linked to this user
|
||||
try {
|
||||
Long cashierId = jdbcTemplate.queryForObject(
|
||||
"SELECT mc.id FROM m_cashiers mc JOIN m_staff ms ON ms.id = mc.staff_id "
|
||||
+ "JOIN m_appuser au ON au.staff_id = ms.id WHERE au.id = ?",
|
||||
Long.class, user.getId());
|
||||
header.setCashierId(cashierId);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// resolving the current user must never block recording the exchange
|
||||
}
|
||||
|
||||
// Validate that cashier has enough of each denomination being GIVEN to the customer
|
||||
if (header.getCashierId() != null) {
|
||||
final java.util.Map<Long, Long> stock = denominationBreakdownWriter.getCashierNetPosition(header.getCashierId());
|
||||
final java.util.List<String> shortages = new java.util.ArrayList<>();
|
||||
for (JsonElement el : given) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count <= 0) continue;
|
||||
if (!item.has("denominationId") || item.get("denominationId").isJsonNull()) continue;
|
||||
long denomId = item.get("denominationId").getAsLong();
|
||||
long available = stock.getOrDefault(denomId, 0L);
|
||||
if (count > available) {
|
||||
int denomValue = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt() : 0;
|
||||
shortages.add("₹" + denomValue + " (need " + count + ", available " + available + ")");
|
||||
}
|
||||
}
|
||||
if (!shortages.isEmpty()) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.cashier.denomination.insufficient",
|
||||
"Insufficient denomination stock for exchange — cannot give: " + String.join("; ", shortages));
|
||||
}
|
||||
}
|
||||
|
||||
CashExchange saved = repository.saveAndFlush(header);
|
||||
|
||||
saveDetails(saved.getId(), "RECEIVED", received);
|
||||
saveDetails(saved.getId(), "GIVEN", given);
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", saved.getId());
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{id}/reverse")
|
||||
public String reverse(@PathParam("id") Long id, String body) {
|
||||
CashExchange exchange = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Cash exchange not found: " + id));
|
||||
if (Boolean.TRUE.equals(exchange.getIsReversed())) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashexchange.already.reversed",
|
||||
"Cash exchange " + id + " is already reversed.");
|
||||
}
|
||||
String reason = null;
|
||||
try {
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
reason = json.has("reason") && !json.get("reason").isJsonNull() ? json.get("reason").getAsString() : null;
|
||||
} catch (Exception ignored) {}
|
||||
exchange.setIsReversed(true);
|
||||
exchange.setReversedAt(LocalDateTime.now());
|
||||
exchange.setReverseReason(reason);
|
||||
try {
|
||||
AppUser user = this.context.authenticatedUser();
|
||||
if (user != null) exchange.setReversedBy(user.getId());
|
||||
} catch (Exception ignored) {}
|
||||
repository.save(exchange);
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("resourceId", id);
|
||||
result.addProperty("reversed", true);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll(
|
||||
@QueryParam("accountId") Long accountId,
|
||||
@QueryParam("offset") @jakarta.ws.rs.DefaultValue("0") int offset,
|
||||
@QueryParam("limit") @jakarta.ws.rs.DefaultValue("20") int limit) {
|
||||
final Long scopedCashierId = cashierRoleHelper.getCashierIdForFiltering();
|
||||
List<CashExchange> all = accountId != null
|
||||
? repository.findByAccountIdOrderByCreatedAtDesc(accountId)
|
||||
: repository.findAllByOrderByCreatedAtDesc();
|
||||
if (scopedCashierId != null) {
|
||||
all = all.stream()
|
||||
.filter(e -> scopedCashierId.equals(e.getCashierId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
final int total = all.size();
|
||||
final int safeOffset = Math.max(0, Math.min(offset, total));
|
||||
final int safeTo = Math.min(safeOffset + Math.max(1, limit), total);
|
||||
final List<Map<String, Object>> page = all.subList(safeOffset, safeTo)
|
||||
.stream().map(this::toMap).collect(Collectors.toList());
|
||||
final Map<String, Object> resp = new LinkedHashMap<>();
|
||||
resp.put("totalFilteredRecords", total);
|
||||
resp.put("offset", safeOffset);
|
||||
resp.put("limit", limit);
|
||||
resp.put("pageItems", page);
|
||||
return gson.toJson(resp);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
public String getOne(@PathParam("id") Long id) {
|
||||
CashExchange header = repository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Cash exchange not found: " + id));
|
||||
Map<String, Object> map = toMap(header);
|
||||
List<Map<String, Object>> details = detailRepository.findByExchangeId(id).stream().map(this::detailToMap)
|
||||
.collect(Collectors.toList());
|
||||
map.put("details", details);
|
||||
return gson.toJson(map);
|
||||
}
|
||||
|
||||
private BigDecimal sumTotal(JsonArray items) {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int value = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt()
|
||||
: 0;
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) {
|
||||
total = total.add(BigDecimal.valueOf((long) value * count));
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private boolean hasPositiveCount(JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveDetails(Long exchangeId, String direction, JsonArray items) {
|
||||
for (JsonElement el : items) {
|
||||
JsonObject item = el.getAsJsonObject();
|
||||
int count = item.has("count") && !item.get("count").isJsonNull() ? item.get("count").getAsInt() : 0;
|
||||
if (count <= 0) {
|
||||
continue;
|
||||
}
|
||||
int value = item.has("denominationValue") && !item.get("denominationValue").isJsonNull()
|
||||
? item.get("denominationValue").getAsInt()
|
||||
: 0;
|
||||
Long denominationId = item.has("denominationId") && !item.get("denominationId").isJsonNull()
|
||||
? item.get("denominationId").getAsLong()
|
||||
: null;
|
||||
CashExchangeDetail detail = new CashExchangeDetail();
|
||||
detail.setExchangeId(exchangeId);
|
||||
detail.setDirection(direction);
|
||||
detail.setDenominationId(denominationId);
|
||||
detail.setDenominationValue(value);
|
||||
detail.setCount(count);
|
||||
detail.setTotalAmount(BigDecimal.valueOf((long) value * count));
|
||||
detailRepository.save(detail);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(CashExchange e) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", e.getId());
|
||||
map.put("cashierId", e.getCashierId());
|
||||
map.put("accountId", e.getAccountId());
|
||||
map.put("accountNo", e.getAccountNo());
|
||||
map.put("clientId", e.getClientId());
|
||||
map.put("customerIdProof", e.getCustomerIdProof());
|
||||
map.put("exchangeAmount", e.getExchangeAmount());
|
||||
map.put("exchangeDate", e.getExchangeDate() != null ? e.getExchangeDate().toString() : null);
|
||||
map.put("note", e.getNote());
|
||||
map.put("performedBy", e.getPerformedBy());
|
||||
map.put("performedByUsername", e.getPerformedByUsername());
|
||||
map.put("createdAt", e.getCreatedAt() != null ? e.getCreatedAt().toString() : null);
|
||||
map.put("isReversed", Boolean.TRUE.equals(e.getIsReversed()));
|
||||
map.put("reversedBy", e.getReversedBy());
|
||||
map.put("reversedAt", e.getReversedAt() != null ? e.getReversedAt().toString() : null);
|
||||
map.put("reverseReason", e.getReverseReason());
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> detailToMap(CashExchangeDetail d) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("id", d.getId());
|
||||
map.put("direction", d.getDirection());
|
||||
map.put("denominationId", d.getDenominationId());
|
||||
map.put("denominationValue", d.getDenominationValue());
|
||||
map.put("count", d.getCount());
|
||||
map.put("totalAmount", d.getTotalAmount());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.cashexchange.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;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_exchange")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashExchange {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "account_id")
|
||||
private Long accountId;
|
||||
|
||||
@Column(name = "account_no", length = 50)
|
||||
private String accountNo;
|
||||
|
||||
@Column(name = "client_id")
|
||||
private Long clientId;
|
||||
|
||||
@Column(name = "exchange_amount", nullable = false)
|
||||
private BigDecimal exchangeAmount;
|
||||
|
||||
@Column(name = "exchange_date")
|
||||
private LocalDate exchangeDate;
|
||||
|
||||
@Column(name = "note", length = 500)
|
||||
private String note;
|
||||
|
||||
@Column(name = "cashier_id")
|
||||
private Long cashierId;
|
||||
|
||||
@Column(name = "customer_id_proof", length = 50)
|
||||
private String customerIdProof;
|
||||
|
||||
@Column(name = "performed_by")
|
||||
private Long performedBy;
|
||||
|
||||
@Column(name = "performed_by_username", length = 100)
|
||||
private String performedByUsername;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "is_reversed")
|
||||
private Boolean isReversed;
|
||||
|
||||
@Column(name = "reversed_by")
|
||||
private Long reversedBy;
|
||||
|
||||
@Column(name = "reversed_at")
|
||||
private LocalDateTime reversedAt;
|
||||
|
||||
@Column(name = "reverse_reason", length = 500)
|
||||
private String reverseReason;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.cashexchange.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;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_cash_exchange_detail")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class CashExchangeDetail {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "exchange_id", nullable = false)
|
||||
private Long exchangeId;
|
||||
|
||||
@Column(name = "direction", nullable = false, length = 10)
|
||||
private String direction;
|
||||
|
||||
@Column(name = "denomination_id")
|
||||
private Long denominationId;
|
||||
|
||||
@Column(name = "denomination_value", nullable = false)
|
||||
private Integer denominationValue;
|
||||
|
||||
@Column(name = "count", nullable = false)
|
||||
private Integer count;
|
||||
|
||||
@Column(name = "total_amount", nullable = false)
|
||||
private BigDecimal totalAmount;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user