Compare commits
79
Commits
| 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 -c commit.gpgsign=false commit -m ' *)",
|
||||||
"Bash(git pull *)",
|
"Bash(git pull *)",
|
||||||
"Bash(./gradlew :fineract-security:compileJava :fineract-provider:compileJava -q)",
|
"Bash(./gradlew :fineract-security:compileJava :fineract-provider:compileJava -q)",
|
||||||
"Bash(git ls-remote *)"
|
"Bash(git ls-remote *)",
|
||||||
|
"Bash(PGPASSWORD=root psql -h localhost -p 5432 -U postgres -d fineract_default -c \"SELECT cv.id, cv.code_value FROM m_code_value cv JOIN m_code c ON cv.code_id=c.id WHERE c.code_name='Constitution' ORDER BY cv.id;\")",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes | %{time_total}s ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-pan -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"panNumber\":\"AQRPV8345E\",\"name\":\"Aditya Vyas\",\"dob\":\"19/12/1988\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes | %{time_total}s ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-pan -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"panNumber\":\"AAMCG5406B\",\"name\":\"GIZMOGLADE PRIVATE LIMITED\",\"dob\":\"01/12/2025\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-pan -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"panNumber\":\"AAMCG5406B\",\"name\":\"\",\"dob\":\"\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-gstn -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"gstNumber\":\"21AAMCG5406BIZ6\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"Bash(curl -s -w '\\\\n--- HTTP %{http_code} | content-type: %{content_type} | %{size_download} bytes | %{time_total}s ---\\\\n' -X POST https://ekyc.lincpay.in/EKYC/ekyc/validate-gstn -H 'Content-Type: application/json' -d '{\"apikey\":\"api_7c9e3a1f6d4b2a8c5e1f3d7b9a2c6e4f\",\"gstNumber\":\"23AADCL9984A1ZH\",\"secrete\":\"secret_4e8a2d7c1f9b3a6e5d2c8f1a7b4e9c3d\"}')",
|
||||||
|
"PowerShell(Get-Process | Where-Object { $_.ProcessName -match '^\\(java|gradle\\)' } | Select-Object Id, ProcessName, StartTime, @{n='CPU\\(s\\)';e={[math]::Round\\($_.CPU,1\\)}}, @{n='WS\\(MB\\)';e={[math]::Round\\($_.WorkingSet64/1MB,0\\)}} | Format-Table -AutoSize)",
|
||||||
|
"PowerShell(Get-NetTCPConnection -LocalPort 8443 -ErrorAction SilentlyContinue)",
|
||||||
|
"PowerShell(Stop-Process -Id 3468,3864,39964 -Force)",
|
||||||
|
"PowerShell(Get-Process -Id 3468,3864,39964 -ErrorAction SilentlyContinue)",
|
||||||
|
"PowerShell(foreach \\($id in 3468,3864,39964\\) { try { Stop-Process -Id $id -Force -ErrorAction Stop; \"killed $id\" } catch { \"skip $id - $\\($_.Exception.Message\\)\" } })",
|
||||||
|
"PowerShell(Get-Process java -ErrorAction SilentlyContinue)",
|
||||||
|
"PowerShell(if \\(-not \\(Test-Path .\\\\fineract-provider\\\\logs\\)\\) { New-Item -ItemType Directory -Force .\\\\fineract-provider\\\\logs | Out-Null }; Remove-Item .\\\\fineract-provider\\\\logs\\\\devrun.out -ErrorAction SilentlyContinue; \"ready\")",
|
||||||
|
"PowerShell($j = Get-Process java -ErrorAction SilentlyContinue | Select-Object Id, @{n='CPU\\(s\\)';e={[math]::Round\\($_.CPU,1\\)}}, @{n='WS\\(MB\\)';e={[math]::Round\\($_.WorkingSet64/1MB,0\\)}}, StartTime; $j | Format-Table -AutoSize; \"---\"; Get-NetTCPConnection -LocalPort 8443 -State Listen -ErrorAction SilentlyContinue | Select-Object LocalAddress, OwningProcess)",
|
||||||
|
"PowerShell(if \\(Test-Path .\\\\fineract-provider\\\\logs\\\\devrun.out\\) { $f = Get-Item .\\\\fineract-provider\\\\logs\\\\devrun.out; \"size=$\\($f.Length\\) bytes mtime=$\\($f.LastWriteTime\\)\" } else { \"devrun.out missing\" })",
|
||||||
|
"PowerShell(netstat -ano)",
|
||||||
|
"PowerShell(Get-Process java -ErrorAction SilentlyContinue | Stop-Process -Force; Start-Sleep -Milliseconds 800; $left = Get-Process java -ErrorAction SilentlyContinue; if \\($left\\) { $left | Select-Object Id, ProcessName } else { \"no java processes\" }; Get-NetTCPConnection -LocalPort 8443 -State Listen -ErrorAction SilentlyContinue | Measure-Object | ForEach-Object { \"8443 listeners: $\\($_.Count\\)\" })",
|
||||||
|
"Bash(curl -sk -o /tmp/resp.txt -w \"STATUS=%{http_code}\\\\nCONTENT_TYPE=%{content_type}\\\\n\" -X POST 'https://localhost:8443/fineract-provider/api/v1/authentication' -H 'Content-Type: application/json' -H 'Fineract-Platform-TenantId: default' -d '{\"username\":\"mifos\",\"password\":\"password\"}')",
|
||||||
|
"Bash(curl -sk -o /tmp/r1.txt -w \"STATUS=%{http_code}\\\\n\" -X POST 'https://localhost:8443/fineract-provider/api/v1/authentication' -H 'Content-Type: application/json' -d '{\"username\":\"mifos\",\"password\":\"password\"}')",
|
||||||
|
"Bash(curl -s -o /tmp/r2.txt -w \"STATUS=%{http_code}\\\\n\" -X POST 'http://localhost:4200/fineract-provider/api/v1/authentication' -H 'Content-Type: application/json' -d '{\"username\":\"mifos\",\"password\":\"password\"}')",
|
||||||
|
"Bash(curl -s -o /tmp/r3.txt -w \"STATUS=%{http_code}\\\\n\" -X POST 'http://localhost:4200/fineract-provider/api/v1/authentication' -H 'Content-Type: application/json' -H 'Origin: http://localhost:4200' -H 'Fineract-Platform-TenantId: default' -d '{\"username\":\"mifos\",\"password\":\"password\"}')",
|
||||||
|
"Bash(curl -s -o /tmp/r4.txt -w \"STATUS=%{http_code}\\\\n\" -X OPTIONS 'http://localhost:4200/fineract-provider/api/v1/authentication' -H 'Origin: http://localhost:4200' -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: content-type,fineract-platform-tenantid' -i)",
|
||||||
|
"Bash(git cherry-pick *)",
|
||||||
|
"PowerShell($parent = 'C:\\\\Users\\\\adity\\\\Music\\\\CBS'; if \\(Test-Path $parent\\) { \"parent exists: $parent\"; Get-ChildItem $parent -Directory | Select-Object -ExpandProperty Name } else { \"MISSING parent: $parent\" }; \"---\"; $target = Join-Path $parent 'LincpayEKYC'; if \\(Test-Path $target\\) { \"TARGET ALREADY EXISTS: $target\" } else { \"target free: $target\" })",
|
||||||
|
"Bash(git clone *)"
|
||||||
],
|
],
|
||||||
"additionalDirectories": [
|
"additionalDirectories": [
|
||||||
"c:\\Users\\adity\\Music\\CBS\\cfcfrms-bank-api",
|
"c:\\Users\\adity\\Music\\CBS\\cfcfrms-bank-api",
|
||||||
|
|||||||
@@ -35,3 +35,6 @@ gradleExp/
|
|||||||
.java-version
|
.java-version
|
||||||
|
|
||||||
.windsurf/
|
.windsurf/
|
||||||
|
|
||||||
|
# Application logs
|
||||||
|
fineract-provider/logs/
|
||||||
+20
-8
@@ -74,7 +74,10 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
|
|||||||
}
|
}
|
||||||
sb.append("from acc_gl_account gl left join m_code_value cv on tag_id=cv.id ");
|
sb.append("from acc_gl_account gl left join m_code_value cv on tag_id=cv.id ");
|
||||||
if (this.associationParametersData.isRunningBalanceRequired()) {
|
if (this.associationParametersData.isRunningBalanceRequired()) {
|
||||||
sb.append("left outer Join acc_gl_journal_entry gl_j on gl_j.account_id = gl.id");
|
sb.append("left outer join acc_gl_journal_entry gl_j on gl_j.id = (")
|
||||||
|
.append("select id from acc_gl_journal_entry ")
|
||||||
|
.append("where account_id = gl.id ")
|
||||||
|
.append("order by id desc limit 1) ");
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@@ -149,10 +152,10 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
|
|||||||
if (firstWhereConditionAdded) {
|
if (firstWhereConditionAdded) {
|
||||||
sql += SQL_AND;
|
sql += SQL_AND;
|
||||||
}
|
}
|
||||||
sql += " ( name like %?% or gl_code like %?% )";
|
sql += " ( name like ? or gl_code like ? )";
|
||||||
parameterArray[arrayPos] = searchParam;
|
parameterArray[arrayPos] = "%" + searchParam + "%";
|
||||||
arrayPos = arrayPos + 1;
|
arrayPos = arrayPos + 1;
|
||||||
parameterArray[arrayPos] = searchParam;
|
parameterArray[arrayPos] = "%" + searchParam + "%";
|
||||||
arrayPos = arrayPos + 1;
|
arrayPos = arrayPos + 1;
|
||||||
firstWhereConditionAdded = true;
|
firstWhereConditionAdded = true;
|
||||||
}
|
}
|
||||||
@@ -197,12 +200,21 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
|
|||||||
final StringBuilder sql = new StringBuilder();
|
final StringBuilder sql = new StringBuilder();
|
||||||
sql.append("select ").append(rm.schema());
|
sql.append("select ").append(rm.schema());
|
||||||
sql.append(" where gl.id = ?");
|
sql.append(" where gl.id = ?");
|
||||||
if (associationParametersData.isRunningBalanceRequired()) {
|
|
||||||
sql.append(" and gl_j.is_running_balance_calculated = true ")
|
final GLAccountData glAccount = this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
|
||||||
.append(" ORDER BY gl_j.entry_date DESC,gl_j.id DESC LIMIT 1");
|
|
||||||
|
// 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 this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
|
return glAccount;
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
throw new GLAccountNotFoundException(glAccountId, e);
|
throw new GLAccountNotFoundException(glAccountId, e);
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -76,6 +76,9 @@ public class Teller extends AbstractPersistableCustom<Long> {
|
|||||||
@Column(name = "state", nullable = false)
|
@Column(name = "state", nullable = false)
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
@Column(name = "is_deleted", nullable = false)
|
||||||
|
private boolean deleted = false;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "teller", fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "teller", fetch = FetchType.LAZY)
|
||||||
private Set<Cashier> cashiers;
|
private Set<Cashier> cashiers;
|
||||||
|
|
||||||
@@ -161,4 +164,12 @@ public class Teller extends AbstractPersistableCustom<Long> {
|
|||||||
this.office.getId();
|
this.office.getId();
|
||||||
this.cashiers.size();
|
this.cashiers.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void delete() {
|
||||||
|
this.deleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleted() {
|
||||||
|
return this.deleted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -31,4 +31,6 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
|||||||
public interface TellerRepository extends JpaRepository<Teller, Long>, JpaSpecificationExecutor<Teller> {
|
public interface TellerRepository extends JpaRepository<Teller, Long>, JpaSpecificationExecutor<Teller> {
|
||||||
|
|
||||||
Collection<Teller> findTellerByOfficeId(Long officeId);
|
Collection<Teller> findTellerByOfficeId(Long officeId);
|
||||||
|
|
||||||
|
long countByDeletedFalse();
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -36,6 +36,9 @@ public class TellerRepositoryWrapper {
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Teller findOneWithNotFoundDetection(final Long id) {
|
public Teller findOneWithNotFoundDetection(final Long id) {
|
||||||
final Teller teller = this.repository.findById(id).orElseThrow(() -> new TellerNotFoundException(id));
|
final Teller teller = this.repository.findById(id).orElseThrow(() -> new TellerNotFoundException(id));
|
||||||
|
if (teller.isDeleted()) {
|
||||||
|
throw new TellerNotFoundException(id);
|
||||||
|
}
|
||||||
teller.initializeLazyCollections();
|
teller.initializeLazyCollections();
|
||||||
return teller;
|
return teller;
|
||||||
}
|
}
|
||||||
@@ -51,4 +54,8 @@ public class TellerRepositoryWrapper {
|
|||||||
public void delete(final Teller teller) {
|
public void delete(final Teller teller) {
|
||||||
this.repository.delete(teller);
|
this.repository.delete(teller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean existsAny() {
|
||||||
|
return this.repository.countByDeletedFalse() > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.organisation.teller.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class TellerAlreadyExistsException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE_CODE = "error.msg.teller.already.exists";
|
||||||
|
private static final String DEFAULT_ERROR_MESSAGE = "Cannot create teller as a teller is already present.";
|
||||||
|
|
||||||
|
public TellerAlreadyExistsException() {
|
||||||
|
super(ERROR_MESSAGE_CODE, DEFAULT_ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-1
@@ -34,7 +34,8 @@ public enum CommandProcessingResultType {
|
|||||||
AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
|
AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
|
||||||
REJECTED(3, "commandProcessingResultType.rejected"), //
|
REJECTED(3, "commandProcessingResultType.rejected"), //
|
||||||
UNDER_PROCESSING(4, "commandProcessingResultType.underProcessing"), //
|
UNDER_PROCESSING(4, "commandProcessingResultType.underProcessing"), //
|
||||||
ERROR(5, "commandProcessingResultType.error");
|
ERROR(5, "commandProcessingResultType.error"), //
|
||||||
|
TERMINATE(6, "commandProcessingResultType.terminate");
|
||||||
|
|
||||||
private static final Map<Integer, CommandProcessingResultType> BY_ID = Arrays.stream(values())
|
private static final Map<Integer, CommandProcessingResultType> BY_ID = Arrays.stream(values())
|
||||||
.collect(Collectors.toMap(CommandProcessingResultType::getValue, v -> v));
|
.collect(Collectors.toMap(CommandProcessingResultType::getValue, v -> v));
|
||||||
@@ -59,4 +60,8 @@ public enum CommandProcessingResultType {
|
|||||||
public boolean isRejected() {
|
public boolean isRejected() {
|
||||||
return this == REJECTED;
|
return this == REJECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTerminate() {
|
||||||
|
return this == TERMINATE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+169
-1
@@ -71,6 +71,166 @@ public class CommandWrapperBuilder {
|
|||||||
this.sanitizeJsonKeys);
|
this.sanitizeJsonKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createChequeSeries() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CHEQUESERIES";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/chequeseries";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder updateChequeSeries(final Long seriesId) {
|
||||||
|
this.actionName = "UPDATE";
|
||||||
|
this.entityName = "CHEQUESERIES";
|
||||||
|
this.entityId = seriesId;
|
||||||
|
this.href = "/chequeseries/" + seriesId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder deleteChequeSeries(final Long seriesId) {
|
||||||
|
this.actionName = "DELETE";
|
||||||
|
this.entityName = "CHEQUESERIES";
|
||||||
|
this.entityId = seriesId;
|
||||||
|
this.href = "/chequeseries/" + seriesId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createChequeStockLot() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CHEQUESTOCKLOT";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/chequestocklots";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder reportLostChequeStockLot(final Long lotId) {
|
||||||
|
this.actionName = "REPORTLOST";
|
||||||
|
this.entityName = "CHEQUESTOCKLOT";
|
||||||
|
this.entityId = lotId;
|
||||||
|
this.href = "/chequestocklots/" + lotId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createChequeStockTransfer() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CHEQUESTOCKTRANSFER";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/chequestocktransfers";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder receiveChequeStockTransfer(final Long transferId) {
|
||||||
|
this.actionName = "RECEIVE";
|
||||||
|
this.entityName = "CHEQUESTOCKTRANSFER";
|
||||||
|
this.entityId = transferId;
|
||||||
|
this.href = "/chequestocktransfers/" + transferId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder rejectChequeStockTransfer(final Long transferId) {
|
||||||
|
this.actionName = "REJECT";
|
||||||
|
this.entityName = "CHEQUESTOCKTRANSFER";
|
||||||
|
this.entityId = transferId;
|
||||||
|
this.href = "/chequestocktransfers/" + transferId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createChequeBook() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/chequebooks";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder issueChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "ISSUE";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder rejectChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "REJECT";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder withdrawChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "WITHDRAW";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder stopPaymentChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "STOPPAYMENT";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder revokeStopPaymentChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "REVOKESTOPPAYMENT";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder reportLostChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "REPORTLOST";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder cancelLeafChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "CANCELLEAF";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder markUsedChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "MARKUSED";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder returnChequeBook(final Long chequeBookId) {
|
||||||
|
this.actionName = "RETURN";
|
||||||
|
this.entityName = "CHEQUEBOOK";
|
||||||
|
this.entityId = chequeBookId;
|
||||||
|
this.href = "/chequebooks/" + chequeBookId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder approveChequeBookPendingRequest(final Long pendingRequestId) {
|
||||||
|
this.actionName = "APPROVE";
|
||||||
|
this.entityName = "CHEQUEBOOKPENDINGREQUEST";
|
||||||
|
this.entityId = pendingRequestId;
|
||||||
|
this.href = "/chequebook/pending-requests/" + pendingRequestId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder rejectChequeBookPendingRequest(final Long pendingRequestId) {
|
||||||
|
this.actionName = "REJECT";
|
||||||
|
this.entityName = "CHEQUEBOOKPENDINGREQUEST";
|
||||||
|
this.entityId = pendingRequestId;
|
||||||
|
this.href = "/chequebook/pending-requests/" + pendingRequestId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder updateCreditBureau() {
|
public CommandWrapperBuilder updateCreditBureau() {
|
||||||
this.actionName = "UPDATE";
|
this.actionName = "UPDATE";
|
||||||
this.entityName = "ORGANISATIONCREDITBUREAU";
|
this.entityName = "ORGANISATIONCREDITBUREAU";
|
||||||
@@ -1611,6 +1771,14 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder terminateAccount(final Long accountId) {
|
||||||
|
this.actionName = "TERMINATE";
|
||||||
|
this.entityName = "ACCOUNT";
|
||||||
|
this.entityId = accountId;
|
||||||
|
this.href = "/accounts/" + accountId + "/terminate";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder approveGSIMAccountApplication(final Long accountId) {
|
public CommandWrapperBuilder approveGSIMAccountApplication(final Long accountId) {
|
||||||
this.actionName = "APPROVE";
|
this.actionName = "APPROVE";
|
||||||
this.entityName = "GSIMACCOUNT";
|
this.entityName = "GSIMACCOUNT";
|
||||||
@@ -3565,7 +3733,7 @@ public class CommandWrapperBuilder {
|
|||||||
this.actionName = "ACTIVATE";
|
this.actionName = "ACTIVATE";
|
||||||
this.entityName = "CURRENTACCOUNT";
|
this.entityName = "CURRENTACCOUNT";
|
||||||
this.savingsId = accountId;
|
this.savingsId = accountId;
|
||||||
this.entityId = null;
|
this.entityId = accountId;
|
||||||
this.href = "/currentaccounts/" + accountId + "?command=activate";
|
this.href = "/currentaccounts/" + accountId + "?command=activate";
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -20,6 +20,7 @@ package org.apache.fineract.commands.service;
|
|||||||
|
|
||||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR;
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR;
|
||||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.PROCESSED;
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.PROCESSED;
|
||||||
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.TERMINATE;
|
||||||
import static org.apache.http.HttpStatus.SC_OK;
|
import static org.apache.http.HttpStatus.SC_OK;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
@@ -181,7 +182,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
currentSource.setResultStatusCode(SC_OK);
|
currentSource.setResultStatusCode(SC_OK);
|
||||||
currentSource.updateForAudit(result);
|
currentSource.updateForAudit(result);
|
||||||
currentSource.setResult(toApiResultJsonSerializer.serializeResult(result));
|
currentSource.setResult(toApiResultJsonSerializer.serializeResult(result));
|
||||||
currentSource.setStatus(PROCESSED);
|
currentSource.setStatus("TERMINATE".equals(wrapper.getActionName()) ? TERMINATE : PROCESSED);
|
||||||
|
|
||||||
// Return saved command source
|
// Return saved command source
|
||||||
return commandSourceService.saveResultSameTransaction(currentSource);
|
return commandSourceService.saveResultSameTransaction(currentSource);
|
||||||
|
|||||||
+34
@@ -21,6 +21,12 @@ package org.apache.fineract.infrastructure.core.serialization;
|
|||||||
import com.google.gson.ExclusionStrategy;
|
import com.google.gson.ExclusionStrategy;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
import 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.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
@@ -112,5 +118,33 @@ public final class GoogleGsonSerializerHelper {
|
|||||||
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
|
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
|
||||||
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
|
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
|
||||||
builder.registerTypeAdapter(ExternalId.class, new ExternalIdAdapter());
|
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"), //
|
ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Savings"), //
|
||||||
JOURNAL_ENTRY_AGGREGATION("Journal Entry Aggregation"), //
|
JOURNAL_ENTRY_AGGREGATION("Journal Entry Aggregation"), //
|
||||||
FETCH_RBI_RSS_FEEDS("Fetch RBI RSS Feeds"), //
|
FETCH_RBI_RSS_FEEDS("Fetch RBI RSS Feeds"), //
|
||||||
|
IMPS_DEEMED_RECONCILE("IMPS DEEMED Reconciliation"), //
|
||||||
|
NEFT_BATCH_PROCESS("NEFT Batch Process"), //
|
||||||
|
VERIFICATION_TOKEN_CLEANUP("Verification Token Cleanup"), //
|
||||||
; //
|
; //
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|||||||
+8
@@ -23,6 +23,7 @@ public enum PortfolioAccountType {
|
|||||||
INVALID(0, "accountType.invalid"), //
|
INVALID(0, "accountType.invalid"), //
|
||||||
LOAN(1, "accountType.loan"), //
|
LOAN(1, "accountType.loan"), //
|
||||||
SAVINGS(2, "accountType.savings"), //
|
SAVINGS(2, "accountType.savings"), //
|
||||||
|
CURRENT(3, "accountType.current"), //
|
||||||
;
|
;
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
@@ -52,6 +53,9 @@ public enum PortfolioAccountType {
|
|||||||
case 2:
|
case 2:
|
||||||
enumType = SAVINGS;
|
enumType = SAVINGS;
|
||||||
break;
|
break;
|
||||||
|
case 3:
|
||||||
|
enumType = CURRENT;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
enumType = INVALID;
|
enumType = INVALID;
|
||||||
}
|
}
|
||||||
@@ -68,4 +72,8 @@ public enum PortfolioAccountType {
|
|||||||
public boolean isLoanAccount() {
|
public boolean isLoanAccount() {
|
||||||
return this.equals(LOAN);
|
return this.equals(LOAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCurrentAccount() {
|
||||||
|
return this.equals(CURRENT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -224,7 +224,7 @@ public final class DepositsApiConstants {
|
|||||||
* Deposit Product Parameters
|
* Deposit Product Parameters
|
||||||
*/
|
*/
|
||||||
private static final Set<String> DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
|
private static final Set<String> DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
|
||||||
monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
|
dateFormatParamName, monthDayFormatParamName, nameParamName, shortNameParamName, descriptionParamName, currencyCodeParamName,
|
||||||
digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName,
|
digitsAfterDecimalParamName, inMultiplesOfParamName, nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName,
|
||||||
interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
|
interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
|
||||||
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, accountingRuleParamName, chargesParamName,
|
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, accountingRuleParamName, chargesParamName,
|
||||||
@@ -512,6 +512,11 @@ public final class DepositsApiConstants {
|
|||||||
fixedDepositInterestCalculationData.add(tenureInMonthsParamName);
|
fixedDepositInterestCalculationData.add(tenureInMonthsParamName);
|
||||||
fixedDepositInterestCalculationData.add(interestPostingPeriodInMonthsParamName);
|
fixedDepositInterestCalculationData.add(interestPostingPeriodInMonthsParamName);
|
||||||
fixedDepositInterestCalculationData.add(interestCompoundingPeriodInMonthsParamName);
|
fixedDepositInterestCalculationData.add(interestCompoundingPeriodInMonthsParamName);
|
||||||
|
// Exact calendar-based calculation params
|
||||||
|
fixedDepositInterestCalculationData.add("submittedOnDate");
|
||||||
|
fixedDepositInterestCalculationData.add("dateFormat");
|
||||||
|
fixedDepositInterestCalculationData.add("locale");
|
||||||
|
fixedDepositInterestCalculationData.add("daysInYear");
|
||||||
|
|
||||||
return fixedDepositInterestCalculationData;
|
return fixedDepositInterestCalculationData;
|
||||||
|
|
||||||
|
|||||||
+1
@@ -170,6 +170,7 @@ public final class SavingsAccountData implements Serializable {
|
|||||||
private Boolean balanceSwapOption;
|
private Boolean balanceSwapOption;
|
||||||
private Boolean msme;
|
private Boolean msme;
|
||||||
private Boolean autoDebitFacility;
|
private Boolean autoDebitFacility;
|
||||||
|
private Boolean terminateTrue;
|
||||||
private String avgBalanceCalcPeriod;
|
private String avgBalanceCalcPeriod;
|
||||||
private String validForPeriod;
|
private String validForPeriod;
|
||||||
private Boolean odAccountClosedZeroBalanceInDayClosing;
|
private Boolean odAccountClosedZeroBalanceInDayClosing;
|
||||||
|
|||||||
+6
@@ -159,6 +159,9 @@ public final class SavingsProductData implements Serializable {
|
|||||||
@Getter @Setter private Map<String, Object> depositLimitKyc;
|
@Getter @Setter private Map<String, Object> depositLimitKyc;
|
||||||
@Getter @Setter private Map<String, List<Map<String, Object>>> otherGlMappingsMap;
|
@Getter @Setter private Map<String, List<Map<String, Object>>> otherGlMappingsMap;
|
||||||
|
|
||||||
|
// True when a row exists in m_saving_product_temp with status = PENDING for this product - that row is the lock.
|
||||||
|
@Getter @Setter private Boolean isPendingApproval;
|
||||||
|
|
||||||
public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
|
public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
|
||||||
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
|
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
|
||||||
final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule,
|
final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule,
|
||||||
@@ -692,6 +695,7 @@ public final class SavingsProductData implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCashBasedAccountingEnabled() {
|
public boolean isCashBasedAccountingEnabled() {
|
||||||
|
if (this.accountingRule == null) return false;
|
||||||
return AccountingRuleType.CASH_BASED.getValue().toString().equals(this.accountingRule.getValue());
|
return AccountingRuleType.CASH_BASED.getValue().toString().equals(this.accountingRule.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,10 +704,12 @@ public final class SavingsProductData implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpfrontAccrualAccounting() {
|
public boolean isUpfrontAccrualAccounting() {
|
||||||
|
if (this.accountingRule == null) return false;
|
||||||
return AccountingRuleType.ACCRUAL_UPFRONT.getValue().toString().equals(this.accountingRule.getValue());
|
return AccountingRuleType.ACCRUAL_UPFRONT.getValue().toString().equals(this.accountingRule.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPeriodicAccrualAccounting() {
|
public boolean isPeriodicAccrualAccounting() {
|
||||||
|
if (this.accountingRule == null) return false;
|
||||||
return AccountingRuleType.ACCRUAL_PERIODIC.getValue().toString().equals(this.accountingRule.getValue());
|
return AccountingRuleType.ACCRUAL_PERIODIC.getValue().toString().equals(this.accountingRule.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.savings.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-side view of a single pending Savings Product edit row (m_savings_product_temp), for the Approvals ->
|
||||||
|
* Pending Savings Product Edits screen.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class SavingsProductTempData implements Serializable {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long savingsProductId;
|
||||||
|
private String savingsProductName;
|
||||||
|
private Long makerId;
|
||||||
|
private String makerUsername;
|
||||||
|
private Long checkerId;
|
||||||
|
private String checkerUsername;
|
||||||
|
private LocalDateTime createdOn;
|
||||||
|
private LocalDateTime checkedOn;
|
||||||
|
private String status;
|
||||||
|
private String rejectReason;
|
||||||
|
private Map<String, Object> proposedChanges;
|
||||||
|
|
||||||
|
public SavingsProductTempData() {}
|
||||||
|
}
|
||||||
+7
-1
@@ -36,7 +36,8 @@ public enum SavingsAccountStatusType {
|
|||||||
REJECTED(500, "savingsAccountStatusType.rejected"), //
|
REJECTED(500, "savingsAccountStatusType.rejected"), //
|
||||||
CLOSED(600, "savingsAccountStatusType.closed"), //
|
CLOSED(600, "savingsAccountStatusType.closed"), //
|
||||||
PRE_MATURE_CLOSURE(700, "savingsAccountStatusType.pre.mature.closure"), //
|
PRE_MATURE_CLOSURE(700, "savingsAccountStatusType.pre.mature.closure"), //
|
||||||
MATURED(800, "savingsAccountStatusType.matured"); //
|
MATURED(800, "savingsAccountStatusType.matured"), //
|
||||||
|
TERMINATED(900, "savingsAccountStatusType.terminated"); //
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
@@ -53,6 +54,7 @@ public enum SavingsAccountStatusType {
|
|||||||
case 600 -> SavingsAccountStatusType.CLOSED;
|
case 600 -> SavingsAccountStatusType.CLOSED;
|
||||||
case 700 -> SavingsAccountStatusType.PRE_MATURE_CLOSURE;
|
case 700 -> SavingsAccountStatusType.PRE_MATURE_CLOSURE;
|
||||||
case 800 -> SavingsAccountStatusType.MATURED;
|
case 800 -> SavingsAccountStatusType.MATURED;
|
||||||
|
case 900 -> SavingsAccountStatusType.TERMINATED;
|
||||||
default -> SavingsAccountStatusType.INVALID;
|
default -> SavingsAccountStatusType.INVALID;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -113,4 +115,8 @@ public enum SavingsAccountStatusType {
|
|||||||
public boolean isPreMatureClosure() {
|
public boolean isPreMatureClosure() {
|
||||||
return this.value.equals(SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue());
|
return this.value.equals(SavingsAccountStatusType.PRE_MATURE_CLOSURE.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return this.value.equals(SavingsAccountStatusType.TERMINATED.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -285,6 +285,11 @@ public final class SavingsEnumerations {
|
|||||||
SavingsAccountStatusType.MATURED.getCode(), "Matured", submittedAndPendingApproval, isApproved, isRejected,
|
SavingsAccountStatusType.MATURED.getCode(), "Matured", submittedAndPendingApproval, isApproved, isRejected,
|
||||||
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
|
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
|
||||||
break;
|
break;
|
||||||
|
case TERMINATED:
|
||||||
|
optionData = new SavingsAccountStatusEnumData(SavingsAccountStatusType.TERMINATED.getValue().longValue(),
|
||||||
|
SavingsAccountStatusType.TERMINATED.getCode(), "Terminated", submittedAndPendingApproval, isApproved, isRejected,
|
||||||
|
isWithdrawnByApplicant, isActive, isClosed, isPrematureClosed, isTransferInProgress, isTransferOnHold, matured);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return optionData;
|
return optionData;
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-5
@@ -39,6 +39,7 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
|
|||||||
import org.apache.fineract.organisation.office.domain.Office;
|
import org.apache.fineract.organisation.office.domain.Office;
|
||||||
import org.apache.fineract.organisation.staff.domain.Staff;
|
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_current_account", uniqueConstraints = {
|
@Table(name = "m_current_account", uniqueConstraints = {
|
||||||
@@ -102,6 +103,30 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
|||||||
@Column(name = "closedon_date")
|
@Column(name = "closedon_date")
|
||||||
private LocalDate closedOnDate;
|
private LocalDate closedOnDate;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "submittedon_userid")
|
||||||
|
private AppUser submittedOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "approvedon_userid")
|
||||||
|
private AppUser approvedOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "activatedon_userid")
|
||||||
|
private AppUser activatedOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "rejectedon_userid")
|
||||||
|
private AppUser rejectedOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "withdrawnon_userid")
|
||||||
|
private AppUser withdrawnOnByUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "closedon_userid")
|
||||||
|
private AppUser closedOnByUser;
|
||||||
|
|
||||||
// Currency (copied from product at account creation)
|
// Currency (copied from product at account creation)
|
||||||
@Column(name = "currency_code", nullable = false, length = 3)
|
@Column(name = "currency_code", nullable = false, length = 3)
|
||||||
private String currencyCode;
|
private String currencyCode;
|
||||||
@@ -304,6 +329,12 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
|||||||
@Column(name = "od_account_closed_zero_balance_in_day_closing")
|
@Column(name = "od_account_closed_zero_balance_in_day_closing")
|
||||||
private boolean odAccountClosedZeroBalanceInDayClosing = false;
|
private boolean odAccountClosedZeroBalanceInDayClosing = false;
|
||||||
|
|
||||||
|
@Column(name = "is_linking_enable", nullable = false)
|
||||||
|
private boolean shareLinkingEnabled = false;
|
||||||
|
|
||||||
|
@Column(name = "terminate_true", nullable = false, columnDefinition = "boolean default false")
|
||||||
|
private boolean terminateTrue = false;
|
||||||
|
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "currentAccount", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
@@ -373,29 +404,34 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
|||||||
|
|
||||||
public void updateAccountNo(String accountNo) { this.accountNo = accountNo; }
|
public void updateAccountNo(String accountNo) { this.accountNo = accountNo; }
|
||||||
|
|
||||||
public void approve(LocalDate approvedOnDate) {
|
public void approve(LocalDate approvedOnDate, AppUser approvedOnByUser) {
|
||||||
this.statusEnum = 200;
|
this.statusEnum = 200;
|
||||||
this.approvedOnDate = approvedOnDate;
|
this.approvedOnDate = approvedOnDate;
|
||||||
|
this.approvedOnByUser = approvedOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void activate(LocalDate activatedOnDate) {
|
public void activate(LocalDate activatedOnDate, AppUser activatedOnByUser) {
|
||||||
this.statusEnum = 300;
|
this.statusEnum = 300;
|
||||||
this.activatedOnDate = activatedOnDate;
|
this.activatedOnDate = activatedOnDate;
|
||||||
|
this.activatedOnByUser = activatedOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reject(LocalDate rejectedOnDate) {
|
public void reject(LocalDate rejectedOnDate, AppUser rejectedOnByUser) {
|
||||||
this.statusEnum = 500;
|
this.statusEnum = 500;
|
||||||
this.rejectedOnDate = rejectedOnDate;
|
this.rejectedOnDate = rejectedOnDate;
|
||||||
|
this.rejectedOnByUser = rejectedOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void withdraw(LocalDate withdrawnOnDate) {
|
public void withdraw(LocalDate withdrawnOnDate, AppUser withdrawnOnByUser) {
|
||||||
this.statusEnum = 400;
|
this.statusEnum = 400;
|
||||||
this.withdrawnOnDate = withdrawnOnDate;
|
this.withdrawnOnDate = withdrawnOnDate;
|
||||||
|
this.withdrawnOnByUser = withdrawnOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close(LocalDate closedOnDate) {
|
public void close(LocalDate closedOnDate, AppUser closedOnByUser) {
|
||||||
this.statusEnum = 600;
|
this.statusEnum = 600;
|
||||||
this.closedOnDate = closedOnDate;
|
this.closedOnDate = closedOnDate;
|
||||||
|
this.closedOnByUser = closedOnByUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMinRequiredOpeningBalance(java.math.BigDecimal val) { this.minRequiredOpeningBalance = val; }
|
public void setMinRequiredOpeningBalance(java.math.BigDecimal val) { this.minRequiredOpeningBalance = val; }
|
||||||
@@ -406,4 +442,15 @@ public class CurrentAccount extends AbstractAuditableCustom {
|
|||||||
public void setNominalAnnualInterestRateOverdraft(java.math.BigDecimal val) { this.nominalAnnualInterestRateOverdraft = val; }
|
public void setNominalAnnualInterestRateOverdraft(java.math.BigDecimal val) { this.nominalAnnualInterestRateOverdraft = val; }
|
||||||
public void setWithdrawalFeeForTransfer(boolean val) { this.withdrawalFeeForTransfer = val; }
|
public void setWithdrawalFeeForTransfer(boolean val) { this.withdrawalFeeForTransfer = val; }
|
||||||
public void setMinOverdraftForInterestCalculation(java.math.BigDecimal val) { this.minOverdraftForInterestCalculation = val; }
|
public void setMinOverdraftForInterestCalculation(java.math.BigDecimal val) { this.minOverdraftForInterestCalculation = val; }
|
||||||
|
|
||||||
|
public boolean isShareLinkingEnabled() { return shareLinkingEnabled; }
|
||||||
|
public void setShareLinkingEnabled(boolean shareLinkingEnabled) { this.shareLinkingEnabled = shareLinkingEnabled; }
|
||||||
|
|
||||||
|
public boolean isTerminateTrue() { return terminateTrue; }
|
||||||
|
public void setTerminateTrue(boolean terminateTrue) { this.terminateTrue = terminateTrue; }
|
||||||
|
|
||||||
|
public void terminate() {
|
||||||
|
this.terminateTrue = true;
|
||||||
|
this.statusEnum = 900;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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 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) {
|
public String generate(CurrentAccount account) {
|
||||||
Long productId = account.getProductId();
|
Long productId = account.getProductId();
|
||||||
|
|
||||||
@@ -59,11 +84,10 @@ public class CurrentAccountNumberGenerator {
|
|||||||
String officeId = StringUtils.leftPad(account.getOffice().getId().toString(), 2, '0');
|
String officeId = StringUtils.leftPad(account.getOffice().getId().toString(), 2, '0');
|
||||||
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
|
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
|
||||||
|
|
||||||
// Account ID — 5 digits
|
// Next sequence from existing account numbers
|
||||||
String accountId = StringUtils.leftPad(account.getId().toString(), 5, '0');
|
String nextSeq = getNextSequence();
|
||||||
if (accountId.length() > 5) accountId = accountId.substring(accountId.length() - 5);
|
|
||||||
|
|
||||||
return CBS_PREFIX + glCode + officeId + accountId;
|
return CBS_PREFIX + glCode + officeId + nextSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String preview(Long productId, Long officeId) {
|
public String preview(Long productId, Long officeId) {
|
||||||
@@ -80,13 +104,10 @@ public class CurrentAccountNumberGenerator {
|
|||||||
String officeStr = StringUtils.leftPad(officeId != null ? officeId.toString() : "0", 2, '0');
|
String officeStr = StringUtils.leftPad(officeId != null ? officeId.toString() : "0", 2, '0');
|
||||||
if (officeStr.length() > 2) officeStr = officeStr.substring(officeStr.length() - 2);
|
if (officeStr.length() > 2) officeStr = officeStr.substring(officeStr.length() - 2);
|
||||||
|
|
||||||
// Next account ID estimate from m_current_account
|
// Next sequence from existing account numbers
|
||||||
Long maxId = jdbcTemplate.query(
|
String nextSeq = getNextSequence();
|
||||||
"SELECT MAX(id) FROM m_current_account",
|
|
||||||
rs -> rs.next() ? rs.getLong(1) : 0L);
|
|
||||||
String nextSeq = StringUtils.leftPad(String.valueOf((maxId != null ? maxId : 0L) + 1), 5, '0');
|
|
||||||
if (nextSeq.length() > 5) nextSeq = nextSeq.substring(nextSeq.length() - 5);
|
|
||||||
|
|
||||||
return CBS_PREFIX + glCode + officeStr + nextSeq;
|
return CBS_PREFIX + glCode + officeStr + nextSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -26,6 +26,8 @@ public interface CurrentAccountReadPlatformService {
|
|||||||
|
|
||||||
Map<String, Object> retrieveOne(Long accountId);
|
Map<String, Object> retrieveOne(Long accountId);
|
||||||
|
|
||||||
|
Map<String, Object> retrieveOneTransaction(Long accountId, Long transactionId);
|
||||||
|
|
||||||
List<Map<String, Object>> retrieveAll();
|
List<Map<String, Object>> retrieveAll();
|
||||||
|
|
||||||
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName);
|
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName);
|
||||||
|
|||||||
+2
@@ -40,4 +40,6 @@ public interface CurrentAccountWritePlatformService {
|
|||||||
CommandProcessingResult modifyApplication(Long accountId, JsonCommand command);
|
CommandProcessingResult modifyApplication(Long accountId, JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult deleteApplication(Long accountId);
|
CommandProcessingResult deleteApplication(Long accountId);
|
||||||
|
|
||||||
|
CommandProcessingResult terminateAccount(Long accountId, JsonCommand command);
|
||||||
}
|
}
|
||||||
|
|||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.currentproduct.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentProductEditPendingApprovalException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentProductEditPendingApprovalException(final Long productId) {
|
||||||
|
super("error.msg.currentproduct.edit.pending.approval",
|
||||||
|
"Current product with identifier " + productId + " already has an edit pending approval", productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.currentproduct.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentProductNoPendingEditException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentProductNoPendingEditException(final Long productId) {
|
||||||
|
super("error.msg.currentproduct.no.pending.edit",
|
||||||
|
"Current product with identifier " + productId + " has no edit pending approval", productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.currentproduct.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentProductSelfApprovalNotAllowedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentProductSelfApprovalNotAllowedException(final Long productId) {
|
||||||
|
super("error.msg.currentproduct.self.approval.not.allowed",
|
||||||
|
"The maker of the pending edit for current product " + productId + " cannot also approve or reject it", productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.currentproduct.service;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.SavingsProductTempData;
|
||||||
|
|
||||||
public interface CurrentProductReadPlatformService {
|
public interface CurrentProductReadPlatformService {
|
||||||
|
|
||||||
@@ -28,4 +29,8 @@ public interface CurrentProductReadPlatformService {
|
|||||||
Collection<SavingsProductData> retrieveAllForLookup();
|
Collection<SavingsProductData> retrieveAllForLookup();
|
||||||
|
|
||||||
SavingsProductData retrieveOne(Long productId);
|
SavingsProductData retrieveOne(Long productId);
|
||||||
|
|
||||||
|
Collection<SavingsProductTempData> retrieveAllPendingEdits();
|
||||||
|
|
||||||
|
SavingsProductTempData retrievePendingEdit(Long productId);
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-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.CurrentProductFacilityConfigRepository;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
||||||
|
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductNoPendingEditException;
|
||||||
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.SavingsProductTempData;
|
||||||
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
||||||
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
|
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
|
||||||
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
import org.springframework.dao.EmptyResultDataAccessException;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEdit;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedFieldRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditChargeRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMappingRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimitRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFieldMapper;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class CurrentProductReadPlatformServiceImpl implements CurrentProductReadPlatformService {
|
public class CurrentProductReadPlatformServiceImpl implements CurrentProductReadPlatformService {
|
||||||
@@ -58,6 +68,11 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
|||||||
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
||||||
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
||||||
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
||||||
|
private final ProductPendingEditRepository productPendingEditRepository;
|
||||||
|
private final ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository;
|
||||||
|
private final ProductPendingEditChargeRepository pendingEditChargeRepository;
|
||||||
|
private final ProductPendingEditGlMappingRepository pendingEditGlMappingRepository;
|
||||||
|
private final ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository;
|
||||||
|
|
||||||
private final CurrentProductMapper currentProductRowMapper = new CurrentProductMapper();
|
private final CurrentProductMapper currentProductRowMapper = new CurrentProductMapper();
|
||||||
private final CurrentProductLookupMapper currentProductLookupsRowMapper = new CurrentProductLookupMapper();
|
private final CurrentProductLookupMapper currentProductLookupsRowMapper = new CurrentProductLookupMapper();
|
||||||
@@ -147,6 +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 static final class CurrentProductMapper implements RowMapper<SavingsProductData> {
|
||||||
|
|
||||||
private final String schemaSql;
|
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.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||||
sqlBuilder.append("cp.balance_swap_option as balanceSwapOption, cp.msme as msme, cp.auto_debit_facility as autoDebitFacility, ");
|
sqlBuilder.append("cp.balance_swap_option as balanceSwapOption, cp.msme as msme, cp.auto_debit_facility as autoDebitFacility, ");
|
||||||
sqlBuilder.append("cp.avg_balance_calc_period as avgBalanceCalcPeriod, cp.valid_for_period as validForPeriod, ");
|
sqlBuilder.append("cp.avg_balance_calc_period as avgBalanceCalcPeriod, cp.valid_for_period as validForPeriod, ");
|
||||||
sqlBuilder.append("cp.od_account_closed_zero_balance_in_day_closing as odAccountClosedZeroBalanceInDayClosing ");
|
sqlBuilder.append("cp.od_account_closed_zero_balance_in_day_closing as odAccountClosedZeroBalanceInDayClosing, ");
|
||||||
|
sqlBuilder.append("exists(select 1 from m_saving_product_temp spt where spt.product_id = cp.id "
|
||||||
|
+ "and spt.product_type = 'CURRENT' and spt.status = 'PENDING') as isPendingApproval ");
|
||||||
|
|
||||||
sqlBuilder.append("from m_current_product cp ");
|
sqlBuilder.append("from m_current_product cp ");
|
||||||
sqlBuilder.append("join m_currency curr on curr.code = cp.currency_code ");
|
sqlBuilder.append("join m_currency curr on curr.code = cp.currency_code ");
|
||||||
@@ -321,6 +396,7 @@ public class CurrentProductReadPlatformServiceImpl implements CurrentProductRead
|
|||||||
productData.setAvgBalanceCalcPeriod(rs.getString("avgBalanceCalcPeriod"));
|
productData.setAvgBalanceCalcPeriod(rs.getString("avgBalanceCalcPeriod"));
|
||||||
productData.setValidForPeriod(rs.getString("validForPeriod"));
|
productData.setValidForPeriod(rs.getString("validForPeriod"));
|
||||||
productData.setOdAccountClosedZeroBalanceInDayClosing(rs.getBoolean("odAccountClosedZeroBalanceInDayClosing"));
|
productData.setOdAccountClosedZeroBalanceInDayClosing(rs.getBoolean("odAccountClosedZeroBalanceInDayClosing"));
|
||||||
|
productData.setIsPendingApproval(rs.getBoolean("isPendingApproval"));
|
||||||
|
|
||||||
return productData;
|
return productData;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
@@ -28,4 +28,21 @@ public interface CurrentProductWritePlatformService {
|
|||||||
CommandProcessingResult update(Long productId, JsonCommand command);
|
CommandProcessingResult update(Long productId, JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult delete(Long productId);
|
CommandProcessingResult delete(Long productId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits an edit for approval instead of applying it directly. Fails if a pending edit already exists for this
|
||||||
|
* product (that pending row is itself the lock).
|
||||||
|
*/
|
||||||
|
CommandProcessingResult submitEditForApproval(Long productId, JsonCommand command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replays the stored pending edit through the normal update path and marks it approved. Fails if the approving
|
||||||
|
* user is the same user who submitted the edit.
|
||||||
|
*/
|
||||||
|
CommandProcessingResult approveEdit(Long productId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discards the pending edit. Fails if the rejecting user is the same user who submitted the edit.
|
||||||
|
*/
|
||||||
|
CommandProcessingResult rejectEdit(Long productId, String rejectReason);
|
||||||
}
|
}
|
||||||
|
|||||||
+245
@@ -31,6 +31,9 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
||||||
|
import org.apache.fineract.commands.domain.CommandProcessingResultType;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSource;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
@@ -38,6 +41,8 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
|
|||||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.charge.domain.Charge;
|
import org.apache.fineract.portfolio.charge.domain.Charge;
|
||||||
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
||||||
@@ -50,10 +55,26 @@ import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilit
|
|||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
|
||||||
|
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductEditPendingApprovalException;
|
||||||
|
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductNoPendingEditException;
|
||||||
|
import org.apache.fineract.portfolio.currentproduct.exception.CurrentProductSelfApprovalNotAllowedException;
|
||||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEdit;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditRepository;
|
||||||
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedField;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedFieldRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditCharge;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditChargeRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimit;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimitRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMapping;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMappingRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFieldMapper;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
|
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -61,6 +82,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements CurrentProductWritePlatformService {
|
public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements CurrentProductWritePlatformService {
|
||||||
|
|
||||||
|
private static final com.google.gson.Gson GSON = new com.google.gson.Gson();
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final CurrentProductRepository currentProductRepository;
|
private final CurrentProductRepository currentProductRepository;
|
||||||
private final CurrentProductDataValidator fromApiJsonDataValidator;
|
private final CurrentProductDataValidator fromApiJsonDataValidator;
|
||||||
@@ -69,6 +91,13 @@ public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements Curr
|
|||||||
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
|
||||||
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
|
||||||
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
|
||||||
|
private final ProductPendingEditRepository productPendingEditRepository;
|
||||||
|
private final ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository;
|
||||||
|
private final ProductPendingEditChargeRepository pendingEditChargeRepository;
|
||||||
|
private final ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository;
|
||||||
|
private final ProductPendingEditGlMappingRepository pendingEditGlMappingRepository;
|
||||||
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
|
private final CommandSourceRepository commandSourceRepository;
|
||||||
|
|
||||||
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dae) {
|
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dae) {
|
||||||
String msgCode = "error.msg." + DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME;
|
String msgCode = "error.msg." + DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME;
|
||||||
@@ -251,6 +280,222 @@ public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements Curr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult submitEditForApproval(final Long productId, final JsonCommand command) {
|
||||||
|
|
||||||
|
final AppUser maker = this.context.authenticatedUser();
|
||||||
|
final CurrentProduct product = this.currentProductRepository.findById(productId)
|
||||||
|
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
|
||||||
|
|
||||||
|
if (this.productPendingEditRepository
|
||||||
|
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_CURRENT, productId, ProductPendingEdit.STATUS_PENDING)
|
||||||
|
.isPresent()) {
|
||||||
|
throw new CurrentProductEditPendingApprovalException(productId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fromApiJsonDataValidator.validateForUpdate(command.json(), product);
|
||||||
|
|
||||||
|
final ProductPendingEdit pendingEdit = new ProductPendingEdit(ProductPendingEdit.TYPE_CURRENT, productId, maker);
|
||||||
|
|
||||||
|
final java.util.Set<String> submitted = ProductPendingEditFieldMapper.populateCoreFields(command, pendingEdit.getCoreFields());
|
||||||
|
final java.util.Set<String> currentOnlySubmitted = ProductPendingEditFieldMapper.populateCurrentOnlyFields(command, pendingEdit.getCurrentOnlyFields());
|
||||||
|
final boolean facilityConfigSubmitted = ProductPendingEditFieldMapper.populateFacilityConfig(command, pendingEdit.getFacilityConfigFields());
|
||||||
|
final boolean depositLimitKycSubmitted = ProductPendingEditFieldMapper.populateDepositLimitKyc(command, pendingEdit.getDepositLimitKycFields());
|
||||||
|
final boolean glMappingSubmitted = ProductPendingEditFieldMapper.populateGlAccountMapping(command, pendingEdit.getGlAccountMappingFields());
|
||||||
|
|
||||||
|
if (!currentOnlySubmitted.isEmpty()) {
|
||||||
|
submitted.add("avgBalanceCalcPeriod");
|
||||||
|
submitted.add("validForPeriod");
|
||||||
|
submitted.add("odAccountClosedZeroBalanceInDayClosing");
|
||||||
|
}
|
||||||
|
if (facilityConfigSubmitted) {
|
||||||
|
submitted.add("accountFacilityConfig");
|
||||||
|
}
|
||||||
|
if (depositLimitKycSubmitted) {
|
||||||
|
submitted.add("depositLimitKyc");
|
||||||
|
}
|
||||||
|
if (glMappingSubmitted) {
|
||||||
|
submitted.add("glAccountMapping");
|
||||||
|
}
|
||||||
|
|
||||||
|
final CommandSource auditEntry = CommandSource.builder() //
|
||||||
|
.actionName("UPDATE") //
|
||||||
|
.entityName("CURRENTPRODUCT") //
|
||||||
|
.resourceGetUrl("/currentproducts/" + productId) //
|
||||||
|
.resourceId(productId) //
|
||||||
|
.commandAsJson(command.json()) //
|
||||||
|
.maker(maker) //
|
||||||
|
.madeOnDate(DateUtils.getAuditOffsetDateTime()) //
|
||||||
|
.status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()) //
|
||||||
|
.idempotencyKey(java.util.UUID.randomUUID().toString()) //
|
||||||
|
.sanitized(false) //
|
||||||
|
.build();
|
||||||
|
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||||
|
pendingEdit.setCommandSourceId(auditEntry.getId());
|
||||||
|
|
||||||
|
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||||
|
|
||||||
|
for (final String fieldName : submitted) {
|
||||||
|
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Long> chargeIds = ProductPendingEditFieldMapper.extractChargeIds(command);
|
||||||
|
if (chargeIds != null) {
|
||||||
|
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, "charges"));
|
||||||
|
for (final Long chargeId : chargeIds) {
|
||||||
|
this.pendingEditChargeRepository.save(new ProductPendingEditCharge(pendingEdit, chargeId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<ProductPendingEditFacilityLimit> facilityLimits = ProductPendingEditFieldMapper.extractFacilityLimits(command, pendingEdit);
|
||||||
|
if (facilityLimits != null) {
|
||||||
|
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, "accountFacilityLimits"));
|
||||||
|
this.pendingEditFacilityLimitRepository.saveAll(facilityLimits);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<ProductPendingEditGlMapping> glMappings = ProductPendingEditFieldMapper.extractGlArrayMappings(command, pendingEdit);
|
||||||
|
if (glMappings != null) {
|
||||||
|
this.pendingEditSubmittedFieldRepository.save(new ProductPendingEditSubmittedField(pendingEdit, "glArrayMappings"));
|
||||||
|
this.pendingEditGlMappingRepository.saveAll(glMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder() //
|
||||||
|
.withEntityId(product.getId()) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult approveEdit(final Long productId) {
|
||||||
|
|
||||||
|
final AppUser checker = this.context.authenticatedUser();
|
||||||
|
final ProductPendingEdit pendingEdit = this.productPendingEditRepository
|
||||||
|
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_CURRENT, productId, ProductPendingEdit.STATUS_PENDING)
|
||||||
|
.orElseThrow(() -> new CurrentProductNoPendingEditException(productId));
|
||||||
|
|
||||||
|
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||||
|
throw new CurrentProductSelfApprovalNotAllowedException(productId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> submittedFields = new java.util.HashSet<>(
|
||||||
|
this.pendingEditSubmittedFieldRepository.findFieldNamesByPendingEditId(pendingEdit.getId()));
|
||||||
|
final JsonObject root = new JsonObject();
|
||||||
|
root.addProperty("locale", "en");
|
||||||
|
root.addProperty("dateFormat", "yyyy-MM-dd");
|
||||||
|
ProductPendingEditFieldMapper.writeCoreFieldsToJson(root, pendingEdit.getCoreFields(), submittedFields);
|
||||||
|
ProductPendingEditFieldMapper.writeCurrentOnlyFieldsToJson(root, pendingEdit.getCurrentOnlyFields(), submittedFields);
|
||||||
|
ProductPendingEditFieldMapper.writeFacilityConfigToJson(root, pendingEdit.getFacilityConfigFields(),
|
||||||
|
submittedFields.contains("accountFacilityConfig"));
|
||||||
|
ProductPendingEditFieldMapper.writeDepositLimitKycToJson(root, pendingEdit.getDepositLimitKycFields(),
|
||||||
|
submittedFields.contains("depositLimitKyc"));
|
||||||
|
if (submittedFields.contains("glAccountMapping")) {
|
||||||
|
ProductPendingEditFieldMapper.writeGlAccountMappingToJson(root, pendingEdit.getGlAccountMappingFields());
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("charges")) {
|
||||||
|
ProductPendingEditFieldMapper.writeChargesToJson(root,
|
||||||
|
this.pendingEditChargeRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("accountFacilityLimits")) {
|
||||||
|
ProductPendingEditFieldMapper.writeFacilityLimitsToJson(root,
|
||||||
|
this.pendingEditFacilityLimitRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("glArrayMappings")) {
|
||||||
|
ProductPendingEditFieldMapper.writeGlArrayMappingsToJson(root,
|
||||||
|
this.pendingEditGlMappingRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
final JsonCommand replayedCommand = JsonCommand.fromJsonElement(productId, root, this.fromApiJsonHelper);
|
||||||
|
final CommandProcessingResult result = update(productId, replayedCommand);
|
||||||
|
|
||||||
|
pendingEdit.markApproved(checker);
|
||||||
|
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||||
|
|
||||||
|
final CommandSource auditEntry = findOrCreateAuditEntry(pendingEdit, productId);
|
||||||
|
auditEntry.markAsChecked(checker);
|
||||||
|
auditEntry.updateForAudit(result);
|
||||||
|
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandSource findOrCreateAuditEntry(final ProductPendingEdit pendingEdit, final Long productId) {
|
||||||
|
if (pendingEdit.getCommandSourceId() != null) {
|
||||||
|
final java.util.Optional<CommandSource> existing = this.commandSourceRepository.findById(pendingEdit.getCommandSourceId());
|
||||||
|
if (existing.isPresent()) {
|
||||||
|
return existing.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final Set<String> submittedFields = new java.util.HashSet<>(
|
||||||
|
this.pendingEditSubmittedFieldRepository.findFieldNamesByPendingEditId(pendingEdit.getId()));
|
||||||
|
final JsonObject root = new JsonObject();
|
||||||
|
root.addProperty("locale", "en");
|
||||||
|
root.addProperty("dateFormat", "yyyy-MM-dd");
|
||||||
|
ProductPendingEditFieldMapper.writeCoreFieldsToJson(root, pendingEdit.getCoreFields(), submittedFields);
|
||||||
|
ProductPendingEditFieldMapper.writeCurrentOnlyFieldsToJson(root, pendingEdit.getCurrentOnlyFields(), submittedFields);
|
||||||
|
ProductPendingEditFieldMapper.writeFacilityConfigToJson(root, pendingEdit.getFacilityConfigFields(),
|
||||||
|
submittedFields.contains("accountFacilityConfig"));
|
||||||
|
ProductPendingEditFieldMapper.writeDepositLimitKycToJson(root, pendingEdit.getDepositLimitKycFields(),
|
||||||
|
submittedFields.contains("depositLimitKyc"));
|
||||||
|
if (submittedFields.contains("glAccountMapping")) {
|
||||||
|
ProductPendingEditFieldMapper.writeGlAccountMappingToJson(root, pendingEdit.getGlAccountMappingFields());
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("charges")) {
|
||||||
|
ProductPendingEditFieldMapper.writeChargesToJson(root,
|
||||||
|
this.pendingEditChargeRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("accountFacilityLimits")) {
|
||||||
|
ProductPendingEditFieldMapper.writeFacilityLimitsToJson(root,
|
||||||
|
this.pendingEditFacilityLimitRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
if (submittedFields.contains("glArrayMappings")) {
|
||||||
|
ProductPendingEditFieldMapper.writeGlArrayMappingsToJson(root,
|
||||||
|
this.pendingEditGlMappingRepository.findByPendingEditId(pendingEdit.getId()));
|
||||||
|
}
|
||||||
|
final String commandJson = GSON.toJson(root);
|
||||||
|
|
||||||
|
final CommandSource auditEntry = CommandSource.builder() //
|
||||||
|
.actionName("UPDATE") //
|
||||||
|
.entityName("CURRENTPRODUCT") //
|
||||||
|
.resourceGetUrl("/currentproducts/" + productId) //
|
||||||
|
.resourceId(productId) //
|
||||||
|
.commandAsJson(commandJson) //
|
||||||
|
.maker(pendingEdit.getMaker()) //
|
||||||
|
.madeOnDate(DateUtils.getAuditOffsetDateTime()) //
|
||||||
|
.status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()) //
|
||||||
|
.idempotencyKey(java.util.UUID.randomUUID().toString()) //
|
||||||
|
.sanitized(false) //
|
||||||
|
.build();
|
||||||
|
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||||
|
pendingEdit.setCommandSourceId(auditEntry.getId());
|
||||||
|
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||||
|
return auditEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult rejectEdit(final Long productId, final String rejectReason) {
|
||||||
|
|
||||||
|
final AppUser checker = this.context.authenticatedUser();
|
||||||
|
final ProductPendingEdit pendingEdit = this.productPendingEditRepository
|
||||||
|
.findByProductTypeAndProductIdAndStatus(ProductPendingEdit.TYPE_CURRENT, productId, ProductPendingEdit.STATUS_PENDING)
|
||||||
|
.orElseThrow(() -> new CurrentProductNoPendingEditException(productId));
|
||||||
|
|
||||||
|
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||||
|
throw new CurrentProductSelfApprovalNotAllowedException(productId);
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingEdit.markRejected(checker, rejectReason);
|
||||||
|
this.productPendingEditRepository.saveAndFlush(pendingEdit);
|
||||||
|
|
||||||
|
final CommandSource auditEntry = findOrCreateAuditEntry(pendingEdit, productId);
|
||||||
|
auditEntry.markAsRejected(checker);
|
||||||
|
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder() //
|
||||||
|
.withEntityId(productId) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult delete(final Long productId) {
|
public CommandProcessingResult delete(final Long productId) {
|
||||||
|
|||||||
+28
-4
@@ -19,7 +19,14 @@
|
|||||||
package org.apache.fineract.portfolio.currentproduct.starter;
|
package org.apache.fineract.portfolio.currentproduct.starter;
|
||||||
|
|
||||||
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditSubmittedFieldRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditChargeRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditFacilityLimitRepository;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.ProductPendingEditGlMappingRepository;
|
||||||
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductAssembler;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductAssembler;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
|
||||||
@@ -43,9 +50,17 @@ public class CurrentProductConfiguration {
|
|||||||
public CurrentProductReadPlatformService currentProductReadPlatformService(PlatformSecurityContext context,
|
public CurrentProductReadPlatformService currentProductReadPlatformService(PlatformSecurityContext context,
|
||||||
JdbcTemplate jdbcTemplate, CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
JdbcTemplate jdbcTemplate, CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
||||||
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
||||||
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
|
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository,
|
||||||
|
ProductPendingEditRepository productPendingEditRepository,
|
||||||
|
ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository,
|
||||||
|
ProductPendingEditChargeRepository pendingEditChargeRepository,
|
||||||
|
ProductPendingEditGlMappingRepository pendingEditGlMappingRepository,
|
||||||
|
ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository) {
|
||||||
return new CurrentProductReadPlatformServiceImpl(context, jdbcTemplate, currentFacilityConfigRepository,
|
return new CurrentProductReadPlatformServiceImpl(context, jdbcTemplate, currentFacilityConfigRepository,
|
||||||
currentFacilityLimitRepository, currentDepositLimitKycRepository);
|
currentFacilityLimitRepository, currentDepositLimitKycRepository,
|
||||||
|
productPendingEditRepository, pendingEditSubmittedFieldRepository,
|
||||||
|
pendingEditChargeRepository, pendingEditGlMappingRepository,
|
||||||
|
pendingEditFacilityLimitRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -56,9 +71,18 @@ public class CurrentProductConfiguration {
|
|||||||
ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
|
ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
|
||||||
CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
|
||||||
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
|
||||||
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
|
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository,
|
||||||
|
ProductPendingEditRepository productPendingEditRepository,
|
||||||
|
ProductPendingEditSubmittedFieldRepository pendingEditSubmittedFieldRepository,
|
||||||
|
ProductPendingEditChargeRepository pendingEditChargeRepository,
|
||||||
|
ProductPendingEditFacilityLimitRepository pendingEditFacilityLimitRepository,
|
||||||
|
ProductPendingEditGlMappingRepository pendingEditGlMappingRepository,
|
||||||
|
FromJsonHelper fromApiJsonHelper,
|
||||||
|
CommandSourceRepository commandSourceRepository) {
|
||||||
return new CurrentProductWritePlatformServiceJpaRepositoryImpl(context, currentProductRepository, currentProductDataValidator,
|
return new CurrentProductWritePlatformServiceJpaRepositoryImpl(context, currentProductRepository, currentProductDataValidator,
|
||||||
currentProductAssembler, accountMappingWritePlatformService, currentFacilityConfigRepository,
|
currentProductAssembler, accountMappingWritePlatformService, currentFacilityConfigRepository,
|
||||||
currentFacilityLimitRepository, currentDepositLimitKycRepository);
|
currentFacilityLimitRepository, currentDepositLimitKycRepository, productPendingEditRepository,
|
||||||
|
pendingEditSubmittedFieldRepository, pendingEditChargeRepository, pendingEditFacilityLimitRepository,
|
||||||
|
pendingEditGlMappingRepository, fromApiJsonHelper, commandSourceRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ dependencies {
|
|||||||
// Bundles ZXing transitively for QR PNG output (used by ZxingPngQrGenerator).
|
// Bundles ZXing transitively for QR PNG output (used by ZxingPngQrGenerator).
|
||||||
'dev.samstevens.totp:totp:1.7.1',
|
'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',
|
'org.apache.commons:commons-lang3',
|
||||||
'commons-io:commons-io',
|
'commons-io:commons-io',
|
||||||
'org.apache.poi:poi',
|
'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
+9712
-12932
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+37030
-34544
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);
|
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.
|
* 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(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
|
||||||
.append(" eao.external_id as externalAssetOwner, ")
|
.append(" eao.external_id as externalAssetOwner, ")
|
||||||
.append(" CASE ")
|
.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 COALESCE(ca.account_no, sa.account_no) ")
|
||||||
.append(" WHEN journalEntry.entity_type_enum = 2 THEN sa.account_no ")
|
|
||||||
.append(" ELSE NULL ")
|
.append(" ELSE NULL ")
|
||||||
.append(" END as savingsAccountNo ");
|
.append(" END as savingsAccountNo ");
|
||||||
if (associationParametersData.isRunningBalanceRequired()) {
|
if (associationParametersData.isRunningBalanceRequired()) {
|
||||||
@@ -130,8 +129,8 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
|||||||
.append(" join m_currency curr on curr.code = journalEntry.currency_code ")
|
.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_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_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_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 AND journalEntry.transaction_id LIKE 'CA%' ");
|
.append(" left join m_current_account ca on ca.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 ");
|
||||||
if (associationParametersData.isTransactionDetailsRequired()) {
|
if (associationParametersData.isTransactionDetailsRequired()) {
|
||||||
sb.append(" left join m_loan_transaction as lt on journalEntry.loan_transaction_id = lt.id ")
|
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 ")
|
.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;
|
int arrayPos = 0;
|
||||||
String whereClose = " where ";
|
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)) {
|
if (StringUtils.isNotBlank(transactionId)) {
|
||||||
sqlBuilder.append(whereClose).append(" journalEntry.transaction_id = ?");
|
sqlBuilder.append(whereClose).append(" journalEntry.transaction_id = ?");
|
||||||
objectArray[arrayPos] = transactionId;
|
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, "
|
"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 "
|
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS ob_credit "
|
||||||
+ "FROM acc_gl_journal_entry je "
|
+ "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(glAccountId);
|
||||||
obParams.add(fromDate);
|
obParams.add(fromDate);
|
||||||
if (officeId != null && officeId > 0) {
|
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, "
|
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS dw_credit, "
|
||||||
+ "COUNT(*) AS dw_count "
|
+ "COUNT(*) AS dw_count "
|
||||||
+ "FROM acc_gl_journal_entry je "
|
+ "FROM acc_gl_journal_entry je "
|
||||||
+ "WHERE 1=1");
|
+ "WHERE (je.reversed IS NULL OR je.reversed = false)");
|
||||||
if (glAccountId != null && glAccountId > 0) {
|
if (glAccountId != null && glAccountId > 0) {
|
||||||
dwSql.append(" AND je.account_id = ?");
|
dwSql.append(" AND je.account_id = ?");
|
||||||
dwParams.add(glAccountId);
|
dwParams.add(glAccountId);
|
||||||
@@ -684,7 +687,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
|||||||
final StringBuilder totSql = new StringBuilder(
|
final StringBuilder totSql = new StringBuilder(
|
||||||
"SELECT COALESCE(SUM(CASE WHEN je.type_enum = 2 THEN je.amount ELSE 0 END), 0) AS totalDebit, "
|
"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 "
|
+ "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) {
|
if (glAccountId != null && glAccountId > 0) {
|
||||||
totSql.append(" AND je.account_id = ?");
|
totSql.append(" AND je.account_id = ?");
|
||||||
totParams.add(glAccountId);
|
totParams.add(glAccountId);
|
||||||
@@ -723,7 +726,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
|||||||
{
|
{
|
||||||
final List<Object> cntParams = new ArrayList<>();
|
final List<Object> cntParams = new ArrayList<>();
|
||||||
final StringBuilder cntSql = new StringBuilder(
|
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) {
|
if (glAccountId != null && glAccountId > 0) {
|
||||||
cntSql.append(" AND je.account_id = ?");
|
cntSql.append(" AND je.account_id = ?");
|
||||||
cntParams.add(glAccountId);
|
cntParams.add(glAccountId);
|
||||||
|
|||||||
+8
@@ -146,6 +146,14 @@ public class SecurityConfig {
|
|||||||
auth.requestMatchers(API_MATCHER.matcher(HttpMethod.OPTIONS, "/api/**")).permitAll()
|
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/*/echo")).permitAll()
|
||||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/authentication")).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,
|
// Login OTP — same posture as PAN/GSTIN public verification: provider-credentialed,
|
||||||
// no Fineract auth required.
|
// no Fineract auth required.
|
||||||
.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/login/otp/generate")).permitAll()
|
.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 {
|
try {
|
||||||
final TellerMapper tm = new TellerMapper();
|
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
|
return this.jdbcTemplate.queryForObject(sql, tm, new Object[] { tellerId }); // NOSONAR
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
@@ -198,7 +198,7 @@ public class TellerManagementReadPlatformServiceImpl implements TellerManagement
|
|||||||
hierarchySearchString = hierarchy + "%";
|
hierarchySearchString = hierarchy + "%";
|
||||||
}
|
}
|
||||||
final TellerMapper tm = new TellerMapper();
|
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
|
return this.jdbcTemplate.query(sql, tm, new Object[] { hierarchySearchString }); // NOSONAR
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-68
@@ -24,13 +24,6 @@ import java.util.Set;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import org.apache.fineract.accounting.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.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
@@ -53,6 +46,7 @@ import org.apache.fineract.organisation.teller.domain.Teller;
|
|||||||
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
|
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
|
||||||
import org.apache.fineract.organisation.teller.exception.CashierExistForTellerException;
|
import org.apache.fineract.organisation.teller.exception.CashierExistForTellerException;
|
||||||
import org.apache.fineract.organisation.teller.exception.CashierNotFoundException;
|
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.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
@@ -70,8 +64,6 @@ public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformSer
|
|||||||
private final StaffRepository staffRepository;
|
private final StaffRepository staffRepository;
|
||||||
private final CashierRepository cashierRepository;
|
private final CashierRepository cashierRepository;
|
||||||
private final CashierTransactionRepository cashierTxnRepository;
|
private final CashierTransactionRepository cashierTxnRepository;
|
||||||
private final JournalEntryRepository glJournalEntryRepository;
|
|
||||||
private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper;
|
|
||||||
private final CashierTransactionDataValidator cashierTransactionDataValidator;
|
private final CashierTransactionDataValidator cashierTransactionDataValidator;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,6 +72,10 @@ public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformSer
|
|||||||
try {
|
try {
|
||||||
this.context.authenticatedUser();
|
this.context.authenticatedUser();
|
||||||
|
|
||||||
|
if (this.tellerRepositoryWrapper.existsAny()) {
|
||||||
|
throw new TellerAlreadyExistsException();
|
||||||
|
}
|
||||||
|
|
||||||
final Long officeId = command.longValueOfParameterNamed("officeId");
|
final Long officeId = command.longValueOfParameterNamed("officeId");
|
||||||
|
|
||||||
this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
|
this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
|
||||||
@@ -161,22 +157,12 @@ public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformSer
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public CommandProcessingResult deleteTeller(Long tellerId) {
|
public CommandProcessingResult deleteTeller(Long tellerId) {
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
Teller teller = tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
|
Teller teller = tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
|
||||||
Set<Cashier> isTellerIdPresentInCashier = teller.getCashiers();
|
teller.delete();
|
||||||
|
tellerRepositoryWrapper.saveAndFlush(teller);
|
||||||
for (final Cashier tellerIdInCashier : isTellerIdPresentInCashier) {
|
|
||||||
if (tellerIdInCashier.getTeller().getId().toString().equalsIgnoreCase(tellerId.toString())) {
|
|
||||||
throw new CashierExistForTellerException(tellerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
tellerRepositoryWrapper.delete(teller);
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder() //
|
||||||
.withEntityId(teller.getId()) //
|
.withEntityId(teller.getId()) //
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -385,53 +371,10 @@ public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformSer
|
|||||||
|
|
||||||
this.cashierTxnRepository.save(cashierTxn);
|
this.cashierTxnRepository.save(cashierTxn);
|
||||||
|
|
||||||
// Pass the journal entries
|
// GL entries skipped for vault-to-cashier (ALLOCATE) and cashier-to-vault (SETTLE)
|
||||||
FinancialActivityAccount mainVaultFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper
|
// transfers. Cash is internal bank physical movement — denomination tracking is
|
||||||
.findByFinancialActivityTypeWithNotFoundDetection(FinancialActivity.CASH_AT_MAINVAULT.getValue());
|
// handled operationally via m_cash_transfer / m_cash_settlement tables.
|
||||||
FinancialActivityAccount tellerCashFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper
|
// GL impact happens only on customer deposit/withdrawal transactions.
|
||||||
.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() //
|
return new CommandProcessingResultBuilder() //
|
||||||
.withCommandId(command.commandId()) //
|
.withCommandId(command.commandId()) //
|
||||||
|
|||||||
+2
-6
@@ -18,8 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.organisation.teller.starter;
|
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.PaginationHelper;
|
||||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
@@ -61,11 +59,9 @@ public class OrganisationTellerConfiguration {
|
|||||||
public TellerWritePlatformService tellerWritePlatformService(PlatformSecurityContext context,
|
public TellerWritePlatformService tellerWritePlatformService(PlatformSecurityContext context,
|
||||||
TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer, TellerRepositoryWrapper tellerRepositoryWrapper,
|
TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer, TellerRepositoryWrapper tellerRepositoryWrapper,
|
||||||
OfficeRepositoryWrapper officeRepositoryWrapper, StaffRepository staffRepository, CashierRepository cashierRepository,
|
OfficeRepositoryWrapper officeRepositoryWrapper, StaffRepository staffRepository, CashierRepository cashierRepository,
|
||||||
CashierTransactionRepository cashierTxnRepository, JournalEntryRepository glJournalEntryRepository,
|
CashierTransactionRepository cashierTxnRepository,
|
||||||
FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper,
|
|
||||||
CashierTransactionDataValidator cashierTransactionDataValidator) {
|
CashierTransactionDataValidator cashierTransactionDataValidator) {
|
||||||
return new TellerWritePlatformServiceJpaImpl(context, fromApiJsonDeserializer, tellerRepositoryWrapper, officeRepositoryWrapper,
|
return new TellerWritePlatformServiceJpaImpl(context, fromApiJsonDeserializer, tellerRepositoryWrapper, officeRepositoryWrapper,
|
||||||
staffRepository, cashierRepository, cashierTxnRepository, glJournalEntryRepository,
|
staffRepository, cashierRepository, cashierTxnRepository, cashierTransactionDataValidator);
|
||||||
financialActivityAccountRepositoryWrapper, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -207,10 +207,10 @@ public class StandingInstructionApiResource {
|
|||||||
if (associationParameters.contains("template")) {
|
if (associationParameters.contains("template")) {
|
||||||
final StandingInstructionData templateData = standingInstructionReadPlatformService.retrieveTemplate(
|
final StandingInstructionData templateData = standingInstructionReadPlatformService.retrieveTemplate(
|
||||||
standingInstructionData.getFromClient().getOfficeId(), standingInstructionData.getFromClient().getId(),
|
standingInstructionData.getFromClient().getOfficeId(), standingInstructionData.getFromClient().getId(),
|
||||||
standingInstructionData.getFromAccount().getId(), standingInstructionData.getFromAccountType().getValue(),
|
standingInstructionData.getFromAccount().getId(), standingInstructionData.fromAccountTypeEnum().getValue(),
|
||||||
standingInstructionData.getToClient().getOfficeId(), standingInstructionData.getToClient().getId(),
|
standingInstructionData.getToClient().getOfficeId(), standingInstructionData.getToClient().getId(),
|
||||||
standingInstructionData.getToAccount().getId(), standingInstructionData.getToAccountType().getValue(),
|
standingInstructionData.getToAccount().getId(), standingInstructionData.toAccountTypeEnum().getValue(),
|
||||||
standingInstructionData.getTransferType().getValue());
|
standingInstructionData.transferTypeEnum().getValue());
|
||||||
standingInstructionData = StandingInstructionData.withTemplateData(standingInstructionData, templateData);
|
standingInstructionData = StandingInstructionData.withTemplateData(standingInstructionData, templateData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -59,7 +59,7 @@ public class AccountTransfersDetailDataValidator {
|
|||||||
|
|
||||||
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
final Integer fromAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(fromAccountTypeParamName, element);
|
||||||
baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType).notNull()
|
baseDataValidator.reset().parameter(fromAccountTypeParamName).value(fromAccountType).notNull()
|
||||||
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2));
|
.isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3));
|
||||||
|
|
||||||
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
final Long toOfficeId = this.fromApiJsonHelper.extractLongNamed(toOfficeIdParamName, element);
|
||||||
baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero();
|
baseDataValidator.reset().parameter(toOfficeIdParamName).value(toOfficeId).notNull().integerGreaterThanZero();
|
||||||
@@ -72,7 +72,7 @@ public class AccountTransfersDetailDataValidator {
|
|||||||
|
|
||||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||||
baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType).notNull().isOneOfTheseValues(Integer.valueOf(1),
|
baseDataValidator.reset().parameter(toAccountTypeParamName).value(toAccountType).notNull().isOneOfTheseValues(Integer.valueOf(1),
|
||||||
Integer.valueOf(2));
|
Integer.valueOf(2), Integer.valueOf(3));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+40
-10
@@ -48,20 +48,27 @@ public final class StandingInstructionData {
|
|||||||
private final Long accountDetailId;
|
private final Long accountDetailId;
|
||||||
@Getter
|
@Getter
|
||||||
private final String name;
|
private final String name;
|
||||||
|
@Getter
|
||||||
private final OfficeData fromOffice;
|
private final OfficeData fromOffice;
|
||||||
@Getter
|
@Getter
|
||||||
private final ClientData fromClient;
|
private final ClientData fromClient;
|
||||||
|
@Getter
|
||||||
private final EnumOptionData fromAccountType;
|
private final EnumOptionData fromAccountType;
|
||||||
@Getter
|
@Getter
|
||||||
private final PortfolioAccountData fromAccount;
|
private final PortfolioAccountData fromAccount;
|
||||||
|
@Getter
|
||||||
private final OfficeData toOffice;
|
private final OfficeData toOffice;
|
||||||
@Getter
|
@Getter
|
||||||
private final ClientData toClient;
|
private final ClientData toClient;
|
||||||
|
@Getter
|
||||||
private final EnumOptionData toAccountType;
|
private final EnumOptionData toAccountType;
|
||||||
@Getter
|
@Getter
|
||||||
private final PortfolioAccountData toAccount;
|
private final PortfolioAccountData toAccount;
|
||||||
|
@Getter
|
||||||
private final EnumOptionData transferType;
|
private final EnumOptionData transferType;
|
||||||
|
@Getter
|
||||||
private final EnumOptionData priority;
|
private final EnumOptionData priority;
|
||||||
|
@Getter
|
||||||
private final EnumOptionData instructionType;
|
private final EnumOptionData instructionType;
|
||||||
@Getter
|
@Getter
|
||||||
private final EnumOptionData status;
|
private final EnumOptionData status;
|
||||||
@@ -69,27 +76,46 @@ public final class StandingInstructionData {
|
|||||||
private final BigDecimal amount;
|
private final BigDecimal amount;
|
||||||
@Getter
|
@Getter
|
||||||
private final LocalDate validFrom;
|
private final LocalDate validFrom;
|
||||||
|
@Getter
|
||||||
private final LocalDate validTill;
|
private final LocalDate validTill;
|
||||||
|
@Getter
|
||||||
private final EnumOptionData recurrenceType;
|
private final EnumOptionData recurrenceType;
|
||||||
|
@Getter
|
||||||
private final EnumOptionData recurrenceFrequency;
|
private final EnumOptionData recurrenceFrequency;
|
||||||
@Getter
|
@Getter
|
||||||
private final Integer recurrenceInterval;
|
private final Integer recurrenceInterval;
|
||||||
|
@Getter
|
||||||
private final MonthDay recurrenceOnMonthDay;
|
private final MonthDay recurrenceOnMonthDay;
|
||||||
|
@Getter
|
||||||
private final Page<AccountTransferData> transactions;
|
private final Page<AccountTransferData> transactions;
|
||||||
|
|
||||||
|
@Getter
|
||||||
private final Collection<OfficeData> fromOfficeOptions;
|
private final Collection<OfficeData> fromOfficeOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<ClientData> fromClientOptions;
|
private final Collection<ClientData> fromClientOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<EnumOptionData> fromAccountTypeOptions;
|
private final Collection<EnumOptionData> fromAccountTypeOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<PortfolioAccountData> fromAccountOptions;
|
private final Collection<PortfolioAccountData> fromAccountOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<OfficeData> toOfficeOptions;
|
private final Collection<OfficeData> toOfficeOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<ClientData> toClientOptions;
|
private final Collection<ClientData> toClientOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<EnumOptionData> toAccountTypeOptions;
|
private final Collection<EnumOptionData> toAccountTypeOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<PortfolioAccountData> toAccountOptions;
|
private final Collection<PortfolioAccountData> toAccountOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<EnumOptionData> transferTypeOptions;
|
private final Collection<EnumOptionData> transferTypeOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<EnumOptionData> statusOptions;
|
private final Collection<EnumOptionData> statusOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<EnumOptionData> instructionTypeOptions;
|
private final Collection<EnumOptionData> instructionTypeOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<EnumOptionData> priorityOptions;
|
private final Collection<EnumOptionData> priorityOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<EnumOptionData> recurrenceTypeOptions;
|
private final Collection<EnumOptionData> recurrenceTypeOptions;
|
||||||
|
@Getter
|
||||||
private final Collection<EnumOptionData> recurrenceFrequencyOptions;
|
private final Collection<EnumOptionData> recurrenceFrequencyOptions;
|
||||||
|
|
||||||
public static StandingInstructionData template(final Collection<OfficeData> fromOfficeOptions,
|
public static StandingInstructionData template(final Collection<OfficeData> fromOfficeOptions,
|
||||||
@@ -282,28 +308,32 @@ public final class StandingInstructionData {
|
|||||||
instructionData.recurrenceTypeOptions, instructionData.recurrenceFrequencyOptions);
|
instructionData.recurrenceTypeOptions, instructionData.recurrenceFrequencyOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public StandingInstructionType getInstructionType() {
|
/**
|
||||||
|
* 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);
|
return Optional.ofNullable(this.instructionType).map(e -> StandingInstructionType.fromInt(e.getId().intValue())).orElse(null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountTransferRecurrenceType getRecurrenceType() {
|
public AccountTransferRecurrenceType recurrenceTypeEnum() {
|
||||||
return Optional.ofNullable(this.recurrenceType).map(e -> AccountTransferRecurrenceType.fromInt(e.getId().intValue())).orElse(null);
|
return Optional.ofNullable(this.recurrenceType).map(e -> AccountTransferRecurrenceType.fromInt(e.getId().intValue())).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PeriodFrequencyType getRecurrenceFrequency() {
|
public PeriodFrequencyType recurrenceFrequencyEnum() {
|
||||||
return Optional.ofNullable(this.recurrenceFrequency).map(e -> PeriodFrequencyType.fromInt(e.getId().intValue())).orElse(null);
|
return Optional.ofNullable(this.recurrenceFrequency).map(e -> PeriodFrequencyType.fromInt(e.getId().intValue())).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PortfolioAccountType getFromAccountType() {
|
public PortfolioAccountType fromAccountTypeEnum() {
|
||||||
return Optional.ofNullable(this.fromAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
return Optional.ofNullable(this.fromAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PortfolioAccountType getToAccountType() {
|
public PortfolioAccountType toAccountTypeEnum() {
|
||||||
return Optional.ofNullable(this.toAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
return Optional.ofNullable(this.toAccountType).map(e -> PortfolioAccountType.fromInt(e.getId().intValue())).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountTransferType getTransferType() {
|
public AccountTransferType transferTypeEnum() {
|
||||||
return Optional.ofNullable(this.transferType).map(e -> AccountTransferType.fromInt(e.getId().intValue())).orElse(null);
|
return Optional.ofNullable(this.transferType).map(e -> AccountTransferType.fromInt(e.getId().intValue())).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,10 +347,10 @@ public final class StandingInstructionData {
|
|||||||
|
|
||||||
public Integer toTransferType() {
|
public Integer toTransferType() {
|
||||||
Integer transferType = null;
|
Integer transferType = null;
|
||||||
AccountTransferType accountTransferType = getTransferType();
|
AccountTransferType accountTransferType = transferTypeEnum();
|
||||||
if (accountTransferType.isChargePayment()) {
|
if (accountTransferType != null && accountTransferType.isChargePayment()) {
|
||||||
transferType = LoanTransactionType.CHARGE_PAYMENT.getValue();
|
transferType = LoanTransactionType.CHARGE_PAYMENT.getValue();
|
||||||
} else if (accountTransferType.isLoanRepayment()) {
|
} else if (accountTransferType != null && accountTransferType.isLoanRepayment()) {
|
||||||
transferType = LoanTransactionType.REPAYMENT.getValue();
|
transferType = LoanTransactionType.REPAYMENT.getValue();
|
||||||
}
|
}
|
||||||
return transferType;
|
return transferType;
|
||||||
|
|||||||
+4
-2
@@ -171,7 +171,8 @@ public class StandingInstructionDataValidator {
|
|||||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull();
|
||||||
|
|
||||||
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element);
|
||||||
if (toAccountType != null && PortfolioAccountType.fromInt(toAccountType).isSavingsAccount()) {
|
if (toAccountType != null && (PortfolioAccountType.fromInt(toAccountType).isSavingsAccount()
|
||||||
|
|| PortfolioAccountType.fromInt(toAccountType).isCurrentAccount())) {
|
||||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType)
|
||||||
.notNull().inMinMaxRange(1, 1);
|
.notNull().inMinMaxRange(1, 1);
|
||||||
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull()
|
||||||
@@ -192,7 +193,8 @@ public class StandingInstructionDataValidator {
|
|||||||
&& (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isLoanAccount())) {
|
&& (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isLoanAccount())) {
|
||||||
errorCode = "not.account.transfer";
|
errorCode = "not.account.transfer";
|
||||||
} else if (accountTransferType.isLoanRepayment()
|
} else if (accountTransferType.isLoanRepayment()
|
||||||
&& (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isSavingsAccount())) {
|
&& (fromPortfolioAccountType.isLoanAccount()
|
||||||
|
|| (toPortfolioAccountType.isSavingsAccount() || toPortfolioAccountType.isCurrentAccount()))) {
|
||||||
errorCode = "not.loan.repayment";
|
errorCode = "not.loan.repayment";
|
||||||
}
|
}
|
||||||
if (errorCode != null) {
|
if (errorCode != null) {
|
||||||
|
|||||||
+101
-1
@@ -34,6 +34,8 @@ import org.apache.fineract.organisation.office.domain.Office;
|
|||||||
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
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.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||||
@@ -49,16 +51,19 @@ public class AccountTransferDetailAssembler {
|
|||||||
private final SavingsAccountAssembler savingsAccountAssembler;
|
private final SavingsAccountAssembler savingsAccountAssembler;
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
private final LoanAssembler loanAccountAssembler;
|
private final LoanAssembler loanAccountAssembler;
|
||||||
|
private final CurrentAccountRepository currentAccountRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public AccountTransferDetailAssembler(final ClientRepositoryWrapper clientRepository,
|
public AccountTransferDetailAssembler(final ClientRepositoryWrapper clientRepository,
|
||||||
final OfficeRepositoryWrapper officeRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
|
final OfficeRepositoryWrapper officeRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
|
||||||
final FromJsonHelper fromApiJsonHelper, final LoanAssembler loanAccountAssembler) {
|
final FromJsonHelper fromApiJsonHelper, final LoanAssembler loanAccountAssembler,
|
||||||
|
final CurrentAccountRepository currentAccountRepository) {
|
||||||
this.clientRepository = clientRepository;
|
this.clientRepository = clientRepository;
|
||||||
this.officeRepositoryWrapper = officeRepositoryWrapper;
|
this.officeRepositoryWrapper = officeRepositoryWrapper;
|
||||||
this.savingsAccountAssembler = savingsAccountAssembler;
|
this.savingsAccountAssembler = savingsAccountAssembler;
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||||
this.loanAccountAssembler = loanAccountAssembler;
|
this.loanAccountAssembler = loanAccountAssembler;
|
||||||
|
this.currentAccountRepository = currentAccountRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
|
||||||
@@ -211,4 +216,99 @@ public class AccountTransferDetailAssembler {
|
|||||||
return AccountTransferDetails.loanToLoanTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toLoanAccount,
|
return AccountTransferDetails.loanToLoanTransfer(fromOffice, fromClient, fromLoanAccount, toOffice, toClient, toLoanAccount,
|
||||||
transferType);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-10
@@ -32,6 +32,7 @@ import java.util.List;
|
|||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
import org.apache.fineract.organisation.office.domain.Office;
|
import org.apache.fineract.organisation.office.domain.Office;
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
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.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||||
|
|
||||||
@@ -71,6 +72,14 @@ public class AccountTransferDetails extends AbstractPersistableCustom<Long> {
|
|||||||
@JoinColumn(name = "from_loan_account_id", nullable = true)
|
@JoinColumn(name = "from_loan_account_id", nullable = true)
|
||||||
private Loan fromLoanAccount;
|
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")
|
@Column(name = "transfer_type")
|
||||||
private Integer transferType;
|
private Integer transferType;
|
||||||
|
|
||||||
@@ -84,21 +93,21 @@ public class AccountTransferDetails extends AbstractPersistableCustom<Long> {
|
|||||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||||
Integer transferType) {
|
Integer transferType) {
|
||||||
|
|
||||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, toOffice, toClient, toSavingsAccount, null,
|
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, null, toOffice, toClient, toSavingsAccount,
|
||||||
transferType, null);
|
null, null, transferType, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AccountTransferDetails savingsToLoanTransfer(final Office fromOffice, final Client fromClient,
|
public static AccountTransferDetails savingsToLoanTransfer(final Office fromOffice, final Client fromClient,
|
||||||
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final Loan toLoanAccount,
|
final SavingsAccount fromSavingsAccount, final Office toOffice, final Client toClient, final Loan toLoanAccount,
|
||||||
Integer transferType) {
|
Integer transferType) {
|
||||||
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, toOffice, toClient, null, toLoanAccount,
|
return new AccountTransferDetails(fromOffice, fromClient, fromSavingsAccount, null, null, toOffice, toClient, null, toLoanAccount,
|
||||||
transferType, null);
|
null, transferType, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AccountTransferDetails loanTosavingsTransfer(final Office fromOffice, final Client fromClient, final Loan fromLoanAccount,
|
public static AccountTransferDetails loanTosavingsTransfer(final Office fromOffice, final Client fromClient, final Loan fromLoanAccount,
|
||||||
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount, Integer transferType) {
|
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount, Integer transferType) {
|
||||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, toOffice, toClient, toSavingsAccount, null,
|
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, null, toOffice, toClient, toSavingsAccount, null,
|
||||||
transferType, null);
|
null, transferType, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AccountTransferDetails() {
|
protected AccountTransferDetails() {
|
||||||
@@ -106,17 +115,21 @@ public class AccountTransferDetails extends AbstractPersistableCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AccountTransferDetails(final Office fromOffice, final Client fromClient, final SavingsAccount fromSavingsAccount,
|
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 fromLoanAccount, final CurrentAccount fromCurrentAccount,
|
||||||
final Loan toLoanAccount, final Integer transferType,
|
final Office toOffice, final Client toClient, final SavingsAccount toSavingsAccount,
|
||||||
|
final Loan toLoanAccount, final CurrentAccount toCurrentAccount,
|
||||||
|
final Integer transferType,
|
||||||
final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
final AccountTransferStandingInstruction accountTransferStandingInstruction) {
|
||||||
this.fromOffice = fromOffice;
|
this.fromOffice = fromOffice;
|
||||||
this.fromClient = fromClient;
|
this.fromClient = fromClient;
|
||||||
this.fromSavingsAccount = fromSavingsAccount;
|
this.fromSavingsAccount = fromSavingsAccount;
|
||||||
this.fromLoanAccount = fromLoanAccount;
|
this.fromLoanAccount = fromLoanAccount;
|
||||||
|
this.fromCurrentAccount = fromCurrentAccount;
|
||||||
this.toOffice = toOffice;
|
this.toOffice = toOffice;
|
||||||
this.toClient = toClient;
|
this.toClient = toClient;
|
||||||
this.toSavingsAccount = toSavingsAccount;
|
this.toSavingsAccount = toSavingsAccount;
|
||||||
this.toLoanAccount = toLoanAccount;
|
this.toLoanAccount = toLoanAccount;
|
||||||
|
this.toCurrentAccount = toCurrentAccount;
|
||||||
this.transferType = transferType;
|
this.transferType = transferType;
|
||||||
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
this.accountTransferStandingInstruction = accountTransferStandingInstruction;
|
||||||
}
|
}
|
||||||
@@ -145,6 +158,14 @@ public class AccountTransferDetails extends AbstractPersistableCustom<Long> {
|
|||||||
return this.fromLoanAccount;
|
return this.fromLoanAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CurrentAccount fromCurrentAccount() {
|
||||||
|
return this.fromCurrentAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CurrentAccount toCurrentAccount() {
|
||||||
|
return this.toCurrentAccount;
|
||||||
|
}
|
||||||
|
|
||||||
public AccountTransferStandingInstruction accountTransferStandingInstruction() {
|
public AccountTransferStandingInstruction accountTransferStandingInstruction() {
|
||||||
return this.accountTransferStandingInstruction;
|
return this.accountTransferStandingInstruction;
|
||||||
}
|
}
|
||||||
@@ -155,8 +176,38 @@ public class AccountTransferDetails extends AbstractPersistableCustom<Long> {
|
|||||||
|
|
||||||
public static AccountTransferDetails loanToLoanTransfer(Office fromOffice, Client fromClient, Loan fromLoanAccount, Office toOffice,
|
public static AccountTransferDetails loanToLoanTransfer(Office fromOffice, Client fromClient, Loan fromLoanAccount, Office toOffice,
|
||||||
Client toClient, Loan toLoanAccount, Integer transferType) {
|
Client toClient, Loan toLoanAccount, Integer transferType) {
|
||||||
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, toOffice, toClient, null, toLoanAccount,
|
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, null, toOffice, toClient, null, toLoanAccount,
|
||||||
transferType, null);
|
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() {
|
public List<AccountTransferTransaction> getAccountTransferTransactions() {
|
||||||
|
|||||||
+15
-3
@@ -249,7 +249,9 @@ public class AccountTransferStandingInstruction extends AbstractPersistableCusto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.accountTransferDetails.toSavingsAccount() != null) {
|
// 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(instructionTypeParamName).value(this.instructionType).notNull().inMinMaxRange(1, 1);
|
||||||
baseDataValidator.reset().parameter(recurrenceTypeParamName).value(this.recurrenceType).notNull().inMinMaxRange(1, 1);
|
baseDataValidator.reset().parameter(recurrenceTypeParamName).value(this.recurrenceType).notNull().inMinMaxRange(1, 1);
|
||||||
}
|
}
|
||||||
@@ -258,12 +260,22 @@ public class AccountTransferStandingInstruction extends AbstractPersistableCusto
|
|||||||
baseDataValidator.reset().parameter(amountParamName).value(this.amount).notNull();
|
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;
|
String errorCode = null;
|
||||||
if (this.accountTransferDetails.transferType().isAccountTransfer()
|
if (this.accountTransferDetails.transferType().isAccountTransfer()
|
||||||
&& (this.accountTransferDetails.fromSavingsAccount() == null || this.accountTransferDetails.toSavingsAccount() == null)) {
|
&& !(fromAccountValid && toAccountValid)) {
|
||||||
errorCode = "not.account.transfer";
|
errorCode = "not.account.transfer";
|
||||||
} else if (this.accountTransferDetails.transferType().isLoanRepayment()
|
} else if (this.accountTransferDetails.transferType().isLoanRepayment()
|
||||||
&& (this.accountTransferDetails.fromSavingsAccount() == null || this.accountTransferDetails.toLoanAccount() == null)) {
|
&& !(fromAccountValidForLoan && this.accountTransferDetails.toLoanAccount() != null)) {
|
||||||
errorCode = "not.loan.repayment";
|
errorCode = "not.loan.repayment";
|
||||||
}
|
}
|
||||||
if (errorCode != null) {
|
if (errorCode != null) {
|
||||||
|
|||||||
+52
-2
@@ -35,6 +35,9 @@ import java.time.LocalDate;
|
|||||||
import java.time.MonthDay;
|
import java.time.MonthDay;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -61,8 +64,13 @@ public class StandingInstructionAssembler {
|
|||||||
BigDecimal amount = null;
|
BigDecimal amount = null;
|
||||||
final BigDecimal transferAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
|
final BigDecimal transferAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
|
||||||
if (transferAmount != null) {
|
if (transferAmount != null) {
|
||||||
final Money monetaryAmount = Money.of(accountTransferDetails.fromSavingsAccount().getCurrency(), transferAmount);
|
final MonetaryCurrency currency = resolveCurrency(accountTransferDetails);
|
||||||
amount = monetaryAmount.getAmount();
|
if (currency != null) {
|
||||||
|
final Money monetaryAmount = Money.of(currency, transferAmount);
|
||||||
|
amount = monetaryAmount.getAmount();
|
||||||
|
} else {
|
||||||
|
amount = transferAmount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final Integer status = command.integerValueOfParameterNamed(statusParamName);
|
final Integer status = command.integerValueOfParameterNamed(statusParamName);
|
||||||
final Integer priority = command.integerValueOfParameterNamed(priorityParamName);
|
final Integer priority = command.integerValueOfParameterNamed(priorityParamName);
|
||||||
@@ -78,6 +86,22 @@ public class StandingInstructionAssembler {
|
|||||||
accountTransferDetails.updateAccountTransferStandingInstruction(accountTransferStandingInstruction);
|
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) {
|
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
|
||||||
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(command);
|
final AccountTransferDetails accountTransferDetails = this.accountTransferDetailAssembler.assembleSavingsToLoanTransfer(command);
|
||||||
assembleStandingInstruction(command, accountTransferDetails);
|
assembleStandingInstruction(command, accountTransferDetails);
|
||||||
@@ -90,4 +114,30 @@ public class StandingInstructionAssembler {
|
|||||||
return 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-1
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.account.jobs.executestandinginstructions;
|
|||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
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.AccountTransfersWritePlatformService;
|
||||||
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
import org.apache.fineract.portfolio.account.service.StandingInstructionReadPlatformService;
|
||||||
import org.springframework.batch.core.Job;
|
import org.springframework.batch.core.Job;
|
||||||
@@ -49,6 +50,8 @@ public class ExecuteStandingInstructionsConfig {
|
|||||||
private DatabaseSpecificSQLGenerator sqlGenerator;
|
private DatabaseSpecificSQLGenerator sqlGenerator;
|
||||||
@Autowired
|
@Autowired
|
||||||
private AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
private AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||||
|
@Autowired
|
||||||
|
private AccountTransferDetailRepository accountTransferDetailRepository;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
protected Step executeStandingInstructionsStep() {
|
protected Step executeStandingInstructionsStep() {
|
||||||
@@ -65,6 +68,6 @@ public class ExecuteStandingInstructionsConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() {
|
public ExecuteStandingInstructionsTasklet executeStandingInstructionsTasklet() {
|
||||||
return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator,
|
return new ExecuteStandingInstructionsTasklet(standingInstructionReadPlatformService, jdbcTemplate, sqlGenerator,
|
||||||
accountTransfersWritePlatformService);
|
accountTransfersWritePlatformService, accountTransferDetailRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -34,6 +34,8 @@ import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
|
|||||||
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
||||||
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
import org.apache.fineract.portfolio.account.data.StandingInstructionData;
|
||||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData;
|
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.AccountTransferRecurrenceType;
|
||||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
|
||||||
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
import org.apache.fineract.portfolio.account.domain.StandingInstructionType;
|
||||||
@@ -58,6 +60,7 @@ public class ExecuteStandingInstructionsTasklet implements Tasklet {
|
|||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||||
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
|
||||||
|
private final AccountTransferDetailRepository accountTransferDetailRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
@@ -66,12 +69,12 @@ public class ExecuteStandingInstructionsTasklet implements Tasklet {
|
|||||||
List<Throwable> errors = new ArrayList<>();
|
List<Throwable> errors = new ArrayList<>();
|
||||||
for (StandingInstructionData data : instructionData) {
|
for (StandingInstructionData data : instructionData) {
|
||||||
boolean isDueForTransfer = false;
|
boolean isDueForTransfer = false;
|
||||||
AccountTransferRecurrenceType recurrenceType = data.getRecurrenceType();
|
AccountTransferRecurrenceType recurrenceType = data.recurrenceTypeEnum();
|
||||||
StandingInstructionType instructionType = data.getInstructionType();
|
StandingInstructionType instructionType = data.instructionTypeEnum();
|
||||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||||
if (recurrenceType.isPeriodicRecurrence()) {
|
if (recurrenceType.isPeriodicRecurrence()) {
|
||||||
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||||
PeriodFrequencyType frequencyType = data.getRecurrenceFrequency();
|
PeriodFrequencyType frequencyType = data.recurrenceFrequencyEnum();
|
||||||
LocalDate startDate = data.getValidFrom();
|
LocalDate startDate = data.getValidFrom();
|
||||||
if (frequencyType.isMonthly()) {
|
if (frequencyType.isMonthly()) {
|
||||||
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay());
|
startDate = startDate.withDayOfMonth(data.getRecurrenceOnDay());
|
||||||
@@ -89,11 +92,11 @@ public class ExecuteStandingInstructionsTasklet implements Tasklet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
BigDecimal transactionAmount = data.getAmount();
|
BigDecimal transactionAmount = data.getAmount();
|
||||||
if (data.getToAccountType().isLoanAccount()
|
if (data.toAccountTypeEnum() != null && data.toAccountTypeEnum().isLoanAccount()
|
||||||
&& (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
|
&& (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
|
||||||
StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService
|
StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService
|
||||||
.retriveLoanDuesData(data.getToAccount().getId());
|
.retriveLoanDuesData(data.getToAccount().getId());
|
||||||
if (data.getInstructionType().isDuesAmoutTransfer()) {
|
if (data.instructionTypeEnum().isDuesAmoutTransfer()) {
|
||||||
transactionAmount = standingInstructionDuesData.totalDueAmount();
|
transactionAmount = standingInstructionDuesData.totalDueAmount();
|
||||||
}
|
}
|
||||||
if (recurrenceType.isDuesRecurrence()) {
|
if (recurrenceType.isDuesRecurrence()) {
|
||||||
@@ -105,10 +108,15 @@ public class ExecuteStandingInstructionsTasklet implements Tasklet {
|
|||||||
final SavingsAccount fromSavingsAccount = null;
|
final SavingsAccount fromSavingsAccount = null;
|
||||||
final boolean isRegularTransaction = true;
|
final boolean isRegularTransaction = true;
|
||||||
final boolean isExceptionForBalanceCheck = false;
|
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,
|
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount,
|
||||||
data.getFromAccountType(), data.getToAccountType(), data.getFromAccount().getId(), data.getToAccount().getId(),
|
data.fromAccountTypeEnum(), data.toAccountTypeEnum(), data.getFromAccount().getId(), data.getToAccount().getId(),
|
||||||
data.getName() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
|
data.getName() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
|
||||||
data.getTransferType().getValue(), null, null, ExternalId.empty(), null, null, fromSavingsAccount,
|
data.transferTypeEnum().getValue(), existingDetails, null, ExternalId.empty(), null, null, fromSavingsAccount,
|
||||||
isRegularTransaction, isExceptionForBalanceCheck);
|
isRegularTransaction, isExceptionForBalanceCheck);
|
||||||
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());
|
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());
|
||||||
|
|
||||||
|
|||||||
+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.");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -52,6 +52,10 @@ public final class AccountTransferEnumerations {
|
|||||||
optionData = new EnumOptionData(PortfolioAccountType.SAVINGS.getValue().longValue(),
|
optionData = new EnumOptionData(PortfolioAccountType.SAVINGS.getValue().longValue(),
|
||||||
PortfolioAccountType.SAVINGS.getCode(), "Savings Account");
|
PortfolioAccountType.SAVINGS.getCode(), "Savings Account");
|
||||||
break;
|
break;
|
||||||
|
case CURRENT:
|
||||||
|
optionData = new EnumOptionData(PortfolioAccountType.CURRENT.getValue().longValue(),
|
||||||
|
PortfolioAccountType.CURRENT.getCode(), "Current Account");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+304
@@ -34,6 +34,9 @@ import java.util.Collection;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
|
||||||
|
import org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity;
|
||||||
|
import org.apache.fineract.accounting.journalentry.service.AccountingProcessorHelper;
|
||||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
@@ -42,6 +45,8 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
|
|||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||||
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
|
||||||
import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator;
|
import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator;
|
||||||
@@ -52,6 +57,10 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferRepository;
|
|||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
|
import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
import org.apache.fineract.portfolio.account.domain.AccountTransferType;
|
||||||
import org.apache.fineract.portfolio.account.exception.DifferentCurrenciesException;
|
import org.apache.fineract.portfolio.account.exception.DifferentCurrenciesException;
|
||||||
|
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
|
||||||
|
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository;
|
||||||
|
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction;
|
||||||
|
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||||
@@ -88,6 +97,9 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
|||||||
private final ConfigurationDomainService configurationDomainService;
|
private final ConfigurationDomainService configurationDomainService;
|
||||||
private final ExternalIdFactory externalIdFactory;
|
private final ExternalIdFactory externalIdFactory;
|
||||||
private final FineractProperties fineractProperties;
|
private final FineractProperties fineractProperties;
|
||||||
|
private final CurrentAccountRepository currentAccountRepository;
|
||||||
|
private final CurrentAccountTransactionRepository currentAccountTransactionRepository;
|
||||||
|
private final AccountingProcessorHelper accountingProcessorHelper;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -448,6 +460,144 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
|||||||
gsim.setParentDeposit(newBalance);
|
gsim.setParentDeposit(newBalance);
|
||||||
gsimRepository.save(gsim);
|
gsimRepository.save(gsim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (isCurrentToSavingsAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
|
||||||
|
// --- Current Account → Savings Account ---
|
||||||
|
final CurrentAccount fromCurrentAccount = currentAccountRepository
|
||||||
|
.findById(accountTransferDTO.getFromAccountId())
|
||||||
|
.orElseThrow(() -> new GeneralPlatformDomainRuleException(
|
||||||
|
"error.msg.currentaccount.not.found", "Current account not found: " + accountTransferDTO.getFromAccountId()));
|
||||||
|
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler
|
||||||
|
.assembleFrom(accountTransferDTO.getToAccountId(), backdatedTxnsAllowedTill);
|
||||||
|
|
||||||
|
final CurrentAccountTransaction withdrawal = performCurrentWithdrawal(fromCurrentAccount,
|
||||||
|
accountTransferDTO.getTransactionAmount(), accountTransferDTO.getTransactionDate(),
|
||||||
|
toSavingsAccount.productId(), toSavingsAccount.getId());
|
||||||
|
|
||||||
|
// skipAccountingPostings=true: current account withdrawal leg (C-txn) already posts
|
||||||
|
// DR CURRENT ACCOUNT / CR SAVING INDIVIDUAL directly — each with its own account number.
|
||||||
|
// Savings deposit GL entries would produce a self-cancelling DR+CR on SAVING INDIVIDUAL — suppress them.
|
||||||
|
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount,
|
||||||
|
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(),
|
||||||
|
accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(),
|
||||||
|
isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill, true);
|
||||||
|
|
||||||
|
if (accountTransferDetails == null) {
|
||||||
|
accountTransferDetails = AccountTransferDetails.currentToSavingsTransfer(
|
||||||
|
fromCurrentAccount.getOffice(), fromCurrentAccount.getClient(), fromCurrentAccount,
|
||||||
|
toSavingsAccount.office(), toSavingsAccount.getClient(), toSavingsAccount,
|
||||||
|
accountTransferDTO.getTransferType());
|
||||||
|
}
|
||||||
|
final Money amount = Money.of(toSavingsAccount.getCurrency(), accountTransferDTO.getTransactionAmount());
|
||||||
|
accountTransferDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToSavingsTransfer(
|
||||||
|
accountTransferDetails, null, deposit, accountTransferDTO.getTransactionDate(), amount,
|
||||||
|
accountTransferDTO.getDescription()));
|
||||||
|
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
|
||||||
|
transferTransactionId = accountTransferDetails.getId();
|
||||||
|
|
||||||
|
} else if (isSavingsToCurrentAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
|
||||||
|
// --- Savings Account → Current Account ---
|
||||||
|
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler
|
||||||
|
.assembleFrom(accountTransferDTO.getFromAccountId(), backdatedTxnsAllowedTill);
|
||||||
|
final CurrentAccount toCurrentAccount = currentAccountRepository
|
||||||
|
.findById(accountTransferDTO.getToAccountId())
|
||||||
|
.orElseThrow(() -> new GeneralPlatformDomainRuleException(
|
||||||
|
"error.msg.currentaccount.not.found", "Current account not found: " + accountTransferDTO.getToAccountId()));
|
||||||
|
|
||||||
|
// skipAccountingPostings=true: current account deposit leg (C-txn) posts
|
||||||
|
// DR SAVING INDIVIDUAL / CR CURRENT ACCOUNT directly. Savings withdrawal GL entries
|
||||||
|
// would produce a self-cancelling DR+CR on the same SAVING INDIVIDUAL GL — suppress them.
|
||||||
|
final SavingsTransactionBooleanValues txnBooleans = new SavingsTransactionBooleanValues(isAccountTransfer,
|
||||||
|
isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), false,
|
||||||
|
accountTransferDTO.isExceptionForBalanceCheck(), true);
|
||||||
|
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
|
||||||
|
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(),
|
||||||
|
accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(),
|
||||||
|
txnBooleans, backdatedTxnsAllowedTill);
|
||||||
|
|
||||||
|
performCurrentDeposit(toCurrentAccount, accountTransferDTO.getTransactionAmount(),
|
||||||
|
accountTransferDTO.getTransactionDate(),
|
||||||
|
fromSavingsAccount.productId(), fromSavingsAccount.getId());
|
||||||
|
|
||||||
|
if (accountTransferDetails == null) {
|
||||||
|
accountTransferDetails = AccountTransferDetails.savingsToCurrentTransfer(
|
||||||
|
fromSavingsAccount.office(), fromSavingsAccount.getClient(), fromSavingsAccount,
|
||||||
|
toCurrentAccount.getOffice(), toCurrentAccount.getClient(), toCurrentAccount,
|
||||||
|
accountTransferDTO.getTransferType());
|
||||||
|
}
|
||||||
|
final Money amount = Money.of(fromSavingsAccount.getCurrency(), accountTransferDTO.getTransactionAmount());
|
||||||
|
accountTransferDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToSavingsTransfer(
|
||||||
|
accountTransferDetails, withdrawal, null, accountTransferDTO.getTransactionDate(), amount,
|
||||||
|
accountTransferDTO.getDescription()));
|
||||||
|
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
|
||||||
|
transferTransactionId = accountTransferDetails.getId();
|
||||||
|
|
||||||
|
} else if (isCurrentToCurrentAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
|
||||||
|
// --- Current Account → Current Account ---
|
||||||
|
final CurrentAccount fromCurrentAccount = currentAccountRepository
|
||||||
|
.findById(accountTransferDTO.getFromAccountId())
|
||||||
|
.orElseThrow(() -> new GeneralPlatformDomainRuleException(
|
||||||
|
"error.msg.currentaccount.not.found", "Current account not found: " + accountTransferDTO.getFromAccountId()));
|
||||||
|
final CurrentAccount toCurrentAccount = currentAccountRepository
|
||||||
|
.findById(accountTransferDTO.getToAccountId())
|
||||||
|
.orElseThrow(() -> new GeneralPlatformDomainRuleException(
|
||||||
|
"error.msg.currentaccount.not.found", "Current account not found: " + accountTransferDTO.getToAccountId()));
|
||||||
|
|
||||||
|
performCurrentWithdrawal(fromCurrentAccount, accountTransferDTO.getTransactionAmount(),
|
||||||
|
accountTransferDTO.getTransactionDate(),
|
||||||
|
toCurrentAccount.getProductId(), toCurrentAccount.getId());
|
||||||
|
performCurrentDeposit(toCurrentAccount, accountTransferDTO.getTransactionAmount(),
|
||||||
|
accountTransferDTO.getTransactionDate(),
|
||||||
|
fromCurrentAccount.getProductId(), fromCurrentAccount.getId());
|
||||||
|
|
||||||
|
if (accountTransferDetails == null) {
|
||||||
|
accountTransferDetails = AccountTransferDetails.currentToCurrentTransfer(
|
||||||
|
fromCurrentAccount.getOffice(), fromCurrentAccount.getClient(), fromCurrentAccount,
|
||||||
|
toCurrentAccount.getOffice(), toCurrentAccount.getClient(), toCurrentAccount,
|
||||||
|
accountTransferDTO.getTransferType());
|
||||||
|
}
|
||||||
|
final MonetaryCurrency currency = new MonetaryCurrency(fromCurrentAccount.getCurrencyCode(),
|
||||||
|
fromCurrentAccount.getCurrencyDigits(), fromCurrentAccount.getCurrencyMultiplesOf());
|
||||||
|
final Money amount = Money.of(currency, accountTransferDTO.getTransactionAmount());
|
||||||
|
accountTransferDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToSavingsTransfer(
|
||||||
|
accountTransferDetails, null, null, accountTransferDTO.getTransactionDate(), amount,
|
||||||
|
accountTransferDTO.getDescription()));
|
||||||
|
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
|
||||||
|
transferTransactionId = accountTransferDetails.getId();
|
||||||
|
|
||||||
|
} else if (isCurrentToLoanAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
|
||||||
|
// --- Current Account → Loan Account ---
|
||||||
|
final CurrentAccount fromCurrentAccount = currentAccountRepository
|
||||||
|
.findById(accountTransferDTO.getFromAccountId())
|
||||||
|
.orElseThrow(() -> new GeneralPlatformDomainRuleException(
|
||||||
|
"error.msg.currentaccount.not.found", "Current account not found: " + accountTransferDTO.getFromAccountId()));
|
||||||
|
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
|
||||||
|
|
||||||
|
performCurrentWithdrawalForLoan(fromCurrentAccount, accountTransferDTO.getTransactionAmount(),
|
||||||
|
accountTransferDTO.getTransactionDate());
|
||||||
|
|
||||||
|
ExternalId externalId = externalIdFactory.create(accountTransferDTO.getTxnExternalId().getValue());
|
||||||
|
final boolean isRecoveryRepayment = false;
|
||||||
|
final Boolean isHolidayValidationDone = false;
|
||||||
|
final String chargeRefundChargeType = null;
|
||||||
|
LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT,
|
||||||
|
toLoanAccount, accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
|
||||||
|
accountTransferDTO.getPaymentDetail(), null, externalId, isRecoveryRepayment,
|
||||||
|
chargeRefundChargeType, isAccountTransfer, null, isHolidayValidationDone);
|
||||||
|
|
||||||
|
final AccountTransferDetails newDetails = AccountTransferDetails.currentToLoanTransfer(
|
||||||
|
fromCurrentAccount.getOffice(), fromCurrentAccount.getClient(), fromCurrentAccount,
|
||||||
|
toLoanAccount.getOffice(), toLoanAccount.client(), toLoanAccount,
|
||||||
|
accountTransferDTO.getTransferType());
|
||||||
|
final MonetaryCurrency currency = new MonetaryCurrency(fromCurrentAccount.getCurrencyCode(),
|
||||||
|
fromCurrentAccount.getCurrencyDigits(), fromCurrentAccount.getCurrencyMultiplesOf());
|
||||||
|
final Money amount = Money.of(currency, accountTransferDTO.getTransactionAmount());
|
||||||
|
newDetails.addAccountTransferTransaction(AccountTransferTransaction.savingsToLoanTransfer(
|
||||||
|
newDetails, null, loanTransaction, accountTransferDTO.getTransactionDate(), amount,
|
||||||
|
accountTransferDTO.getDescription()));
|
||||||
|
this.accountTransferDetailRepository.saveAndFlush(newDetails);
|
||||||
|
transferTransactionId = newDetails.getId();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new GeneralPlatformDomainRuleException("error.msg.accounttransfer.loan.to.loan.not.supported",
|
throw new GeneralPlatformDomainRuleException("error.msg.accounttransfer.loan.to.loan.not.supported",
|
||||||
"Account transfer from loan to another loan is not supported");
|
"Account transfer from loan to another loan is not supported");
|
||||||
@@ -505,6 +655,160 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
|
|||||||
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
|
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 isCurrentToCurrentAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||||
|
final PortfolioAccountType toAccountType) {
|
||||||
|
return fromAccountType.isCurrentAccount() && toAccountType.isCurrentAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCurrentToLoanAccountTransfer(final PortfolioAccountType fromAccountType,
|
||||||
|
final PortfolioAccountType toAccountType) {
|
||||||
|
return fromAccountType.isCurrentAccount() && toAccountType.isLoanAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Withdraw for Current → Loan transfers. Loan domain service handles the loan-side GL,
|
||||||
|
* so current account side uses LIABILITY_TRANSFER as the counterpart (classic approach).
|
||||||
|
*/
|
||||||
|
private CurrentAccountTransaction performCurrentWithdrawalForLoan(final CurrentAccount account, final BigDecimal amount,
|
||||||
|
final LocalDate txnDate) {
|
||||||
|
final BigDecimal balance = account.getAccountBalanceDerived() != null ? account.getAccountBalanceDerived() : BigDecimal.ZERO;
|
||||||
|
final BigDecimal onHold = account.getOnHoldFundsDerived() != null ? account.getOnHoldFundsDerived() : BigDecimal.ZERO;
|
||||||
|
final BigDecimal available = balance.subtract(onHold);
|
||||||
|
if (!account.isAllowOverdraft() && amount.compareTo(available) > 0) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.current.account.insufficient.balance",
|
||||||
|
"Insufficient balance in current account " + account.getId()
|
||||||
|
+ ". Available: " + available + ", Requested: " + amount, account.getId());
|
||||||
|
}
|
||||||
|
final CurrentAccountTransaction txn = CurrentAccountTransaction.withdrawal(account, account.getOffice(), amount, txnDate, null);
|
||||||
|
final BigDecimal newBalance = balance.subtract(amount);
|
||||||
|
txn.setRunningBalanceDerived(newBalance);
|
||||||
|
currentAccountTransactionRepository.saveAndFlush(txn);
|
||||||
|
account.setAccountBalanceDerived(newBalance);
|
||||||
|
account.setTotalWithdrawalsDerived(
|
||||||
|
(account.getTotalWithdrawalsDerived() != null ? account.getTotalWithdrawalsDerived() : BigDecimal.ZERO).add(amount));
|
||||||
|
currentAccountRepository.save(account);
|
||||||
|
if (account.getProductId() != null && txn.getId() != null) {
|
||||||
|
final String currentTxnId = "CA" + txn.getId();
|
||||||
|
accountingProcessorHelper.createCashBasedJournalEntriesAndReversalsForSavings(account.getOffice(),
|
||||||
|
account.getCurrencyCode(), CashAccountsForSavings.SAVINGS_CONTROL.getValue(),
|
||||||
|
FinancialActivity.LIABILITY_TRANSFER.getValue(), account.getProductId(), null,
|
||||||
|
account.getId(), currentTxnId, txnDate, amount, false);
|
||||||
|
}
|
||||||
|
return txn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Withdraw amount from a current account and save the transaction.
|
||||||
|
* Throws {@link GeneralPlatformDomainRuleException} if balance is insufficient.
|
||||||
|
*/
|
||||||
|
private CurrentAccountTransaction performCurrentWithdrawal(final CurrentAccount account, final BigDecimal amount,
|
||||||
|
final LocalDate txnDate, final Long counterpartProductId, final Long counterpartEntityId) {
|
||||||
|
final BigDecimal balance = account.getAccountBalanceDerived() != null ? account.getAccountBalanceDerived() : BigDecimal.ZERO;
|
||||||
|
final BigDecimal onHold = account.getOnHoldFundsDerived() != null ? account.getOnHoldFundsDerived() : BigDecimal.ZERO;
|
||||||
|
final BigDecimal available = balance.subtract(onHold);
|
||||||
|
if (!account.isAllowOverdraft() && amount.compareTo(available) > 0) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.current.account.insufficient.balance",
|
||||||
|
"Insufficient balance in current account " + account.getId()
|
||||||
|
+ ". Available: " + available + ", Requested: " + amount, account.getId());
|
||||||
|
}
|
||||||
|
final CurrentAccountTransaction txn = CurrentAccountTransaction.withdrawal(account, account.getOffice(), amount, txnDate, null);
|
||||||
|
final BigDecimal newBalance = balance.subtract(amount);
|
||||||
|
txn.setRunningBalanceDerived(newBalance);
|
||||||
|
currentAccountTransactionRepository.saveAndFlush(txn);
|
||||||
|
account.setAccountBalanceDerived(newBalance);
|
||||||
|
account.setTotalWithdrawalsDerived(
|
||||||
|
(account.getTotalWithdrawalsDerived() != null ? account.getTotalWithdrawalsDerived() : BigDecimal.ZERO).add(amount));
|
||||||
|
currentAccountRepository.save(account);
|
||||||
|
// Post journal entries: DR current account GL, CR counterpart GL — each with correct entity/account number
|
||||||
|
postCurrentAccountTransferJournalEntries(account, txn, amount, txnDate, true, counterpartProductId, counterpartEntityId);
|
||||||
|
return txn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deposit amount into a current account and save the transaction.
|
||||||
|
*/
|
||||||
|
private CurrentAccountTransaction performCurrentDeposit(final CurrentAccount account, final BigDecimal amount,
|
||||||
|
final LocalDate txnDate, final Long counterpartProductId, final Long counterpartEntityId) {
|
||||||
|
final BigDecimal balance = account.getAccountBalanceDerived() != null ? account.getAccountBalanceDerived() : BigDecimal.ZERO;
|
||||||
|
final CurrentAccountTransaction txn = CurrentAccountTransaction.deposit(account, account.getOffice(), amount, txnDate, null);
|
||||||
|
final BigDecimal newBalance = balance.add(amount);
|
||||||
|
txn.setRunningBalanceDerived(newBalance);
|
||||||
|
currentAccountTransactionRepository.saveAndFlush(txn);
|
||||||
|
account.setAccountBalanceDerived(newBalance);
|
||||||
|
account.setTotalDepositsDerived(
|
||||||
|
(account.getTotalDepositsDerived() != null ? account.getTotalDepositsDerived() : BigDecimal.ZERO).add(amount));
|
||||||
|
currentAccountRepository.save(account);
|
||||||
|
// Post journal entries: DR counterpart GL, CR current account GL — each with correct entity/account number
|
||||||
|
postCurrentAccountTransferJournalEntries(account, txn, amount, txnDate, false, counterpartProductId, counterpartEntityId);
|
||||||
|
return txn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts double-entry journal entries for a current account leg of an account transfer.
|
||||||
|
* <p>
|
||||||
|
* Current Product GL mappings are stored under {@code PortfolioProductType.SAVING}, so the
|
||||||
|
* savings JE helper resolves them correctly.
|
||||||
|
* <p>
|
||||||
|
* Transaction id is prefixed with "C" (e.g. {@code C57}) so the entries are NOT grouped with
|
||||||
|
* savings entries that share the same numeric id (those use prefix "S"). The helper only
|
||||||
|
* appends its own "S" prefix when the id is numeric; passing "C57" keeps it as-is.
|
||||||
|
*
|
||||||
|
* @param isWithdrawal {@code true} for the from-leg (DR control / CR liability transfer);
|
||||||
|
* {@code false} for the to-leg (DR liability transfer / CR control)
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Posts direct transfer journal entries between a current account and a counterpart savings/current account.
|
||||||
|
* Each entry carries the correct entity ID so the correct account number appears on each GL line.
|
||||||
|
*
|
||||||
|
* For withdrawal (current → savings):
|
||||||
|
* DR: current product GL (entity = current account)
|
||||||
|
* CR: counterpart product GL (entity = savings/current account)
|
||||||
|
*
|
||||||
|
* For deposit (savings → current):
|
||||||
|
* DR: counterpart product GL (entity = savings/current account)
|
||||||
|
* CR: current product GL (entity = current account)
|
||||||
|
*
|
||||||
|
* @param counterpartProductId savings/current product ID of the other account
|
||||||
|
* @param counterpartEntityId savings/current account ID of the other account
|
||||||
|
*/
|
||||||
|
private void postCurrentAccountTransferJournalEntries(final CurrentAccount account, final CurrentAccountTransaction txn,
|
||||||
|
final BigDecimal amount, final LocalDate txnDate, final boolean isWithdrawal,
|
||||||
|
final Long counterpartProductId, final Long counterpartEntityId) {
|
||||||
|
if (account.getProductId() == null || txn.getId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Prefix "CA" so:
|
||||||
|
// 1. The helper does NOT re-prefix with "S" (avoids txn id collision with savings "S" prefix)
|
||||||
|
// 2. JournalEntryReadPlatformServiceImpl joins m_current_account (transaction_id LIKE 'CA%')
|
||||||
|
// so the correct current account number appears on the DR/CR entry.
|
||||||
|
final String currentTxnId = "CA" + txn.getId();
|
||||||
|
if (isWithdrawal) {
|
||||||
|
// DR: current account GL CR: counterpart (savings/current) GL
|
||||||
|
accountingProcessorHelper.createDirectTransferJournalEntriesForSavings(
|
||||||
|
account.getOffice(), account.getCurrencyCode(),
|
||||||
|
account.getProductId(), account.getId(),
|
||||||
|
counterpartProductId, counterpartEntityId,
|
||||||
|
currentTxnId, txnDate, amount);
|
||||||
|
} else {
|
||||||
|
// DR: counterpart (savings/current) GL CR: current account GL
|
||||||
|
accountingProcessorHelper.createDirectTransferJournalEntriesForSavings(
|
||||||
|
account.getOffice(), account.getCurrencyCode(),
|
||||||
|
counterpartProductId, counterpartEntityId,
|
||||||
|
account.getProductId(), account.getId(),
|
||||||
|
currentTxnId, txnDate, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public CommandProcessingResult refundByTransfer(JsonCommand command) {
|
public CommandProcessingResult refundByTransfer(JsonCommand command) {
|
||||||
|
|||||||
+86
@@ -43,12 +43,14 @@ public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccount
|
|||||||
private final PortfolioSavingsAccountMapper savingsAccountMapper;
|
private final PortfolioSavingsAccountMapper savingsAccountMapper;
|
||||||
private final PortfolioLoanAccountMapper loanAccountMapper;
|
private final PortfolioLoanAccountMapper loanAccountMapper;
|
||||||
private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper;
|
private final PortfolioLoanAccountRefundByTransferMapper accountRefundByTransferMapper;
|
||||||
|
private final PortfolioCurrentAccountMapper currentAccountMapper;
|
||||||
|
|
||||||
public PortfolioAccountReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator) {
|
public PortfolioAccountReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate, DatabaseSpecificSQLGenerator sqlGenerator) {
|
||||||
this.jdbcTemplate = jdbcTemplate;
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
|
this.savingsAccountMapper = new PortfolioSavingsAccountMapper();
|
||||||
this.loanAccountMapper = new PortfolioLoanAccountMapper();
|
this.loanAccountMapper = new PortfolioLoanAccountMapper();
|
||||||
this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper(sqlGenerator);
|
this.accountRefundByTransferMapper = new PortfolioLoanAccountRefundByTransferMapper(sqlGenerator);
|
||||||
|
this.currentAccountMapper = new PortfolioCurrentAccountMapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -86,6 +88,15 @@ public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccount
|
|||||||
|
|
||||||
accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams);
|
accountData = this.jdbcTemplate.queryForObject(sql, this.savingsAccountMapper, sqlParams);
|
||||||
break;
|
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) {
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
throw new AccountTransferNotFoundException(accountId, e);
|
throw new AccountTransferNotFoundException(accountId, e);
|
||||||
@@ -157,6 +168,23 @@ public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccount
|
|||||||
|
|
||||||
accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams.toArray()); // NOSONAR
|
accounts = this.jdbcTemplate.query(sql, this.savingsAccountMapper, sqlParams.toArray()); // NOSONAR
|
||||||
break;
|
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;
|
return accounts;
|
||||||
@@ -393,4 +421,62 @@ public class PortfolioAccountReadPlatformServiceImpl implements PortfolioAccount
|
|||||||
|
|
||||||
return accountData;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-7
@@ -109,20 +109,21 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
|
|||||||
|
|
||||||
final EnumOptionData loanAccountType = accountType(PortfolioAccountType.LOAN);
|
final EnumOptionData loanAccountType = accountType(PortfolioAccountType.LOAN);
|
||||||
final EnumOptionData savingsAccountType = accountType(PortfolioAccountType.SAVINGS);
|
final EnumOptionData savingsAccountType = accountType(PortfolioAccountType.SAVINGS);
|
||||||
|
final EnumOptionData currentAccountType = accountType(PortfolioAccountType.CURRENT);
|
||||||
|
|
||||||
final Integer mostRelevantFromAccountType = fromAccountType;
|
final Integer mostRelevantFromAccountType = fromAccountType;
|
||||||
Collection<EnumOptionData> fromAccountTypeOptions = null;
|
Collection<EnumOptionData> fromAccountTypeOptions = null;
|
||||||
Collection<EnumOptionData> toAccountTypeOptions = null;
|
Collection<EnumOptionData> toAccountTypeOptions = null;
|
||||||
|
|
||||||
if (accountTransferType.isAccountTransfer()) {
|
if (accountTransferType.isAccountTransfer()) {
|
||||||
fromAccountTypeOptions = Arrays.asList(savingsAccountType);
|
fromAccountTypeOptions = Arrays.asList(savingsAccountType, currentAccountType);
|
||||||
toAccountTypeOptions = Arrays.asList(savingsAccountType);
|
toAccountTypeOptions = Arrays.asList(savingsAccountType, currentAccountType);
|
||||||
} else if (accountTransferType.isLoanRepayment()) {
|
} else if (accountTransferType.isLoanRepayment()) {
|
||||||
fromAccountTypeOptions = Arrays.asList(savingsAccountType);
|
fromAccountTypeOptions = Arrays.asList(savingsAccountType, currentAccountType);
|
||||||
toAccountTypeOptions = Arrays.asList(loanAccountType);
|
toAccountTypeOptions = Arrays.asList(loanAccountType);
|
||||||
} else {
|
} else {
|
||||||
fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType);
|
fromAccountTypeOptions = Arrays.asList(savingsAccountType, currentAccountType, loanAccountType);
|
||||||
toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType);
|
toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType, currentAccountType);
|
||||||
}
|
}
|
||||||
final Integer mostRelevantToAccountType = toAccountType;
|
final Integer mostRelevantToAccountType = toAccountType;
|
||||||
|
|
||||||
@@ -152,6 +153,8 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
|
|||||||
Integer accountType;
|
Integer accountType;
|
||||||
if (mostRelevantFromAccountType == 1) {
|
if (mostRelevantFromAccountType == 1) {
|
||||||
accountType = PortfolioAccountType.LOAN.getValue();
|
accountType = PortfolioAccountType.LOAN.getValue();
|
||||||
|
} else if (mostRelevantFromAccountType == 3) {
|
||||||
|
accountType = PortfolioAccountType.CURRENT.getValue();
|
||||||
} else {
|
} else {
|
||||||
accountType = PortfolioAccountType.SAVINGS.getValue();
|
accountType = PortfolioAccountType.SAVINGS.getValue();
|
||||||
}
|
}
|
||||||
@@ -261,7 +264,8 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
|
|||||||
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
|
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
|
||||||
sqlBuilder.append(this.standingInstructionMapper.schema());
|
sqlBuilder.append(this.standingInstructionMapper.schema());
|
||||||
if (standingInstructionDTO.transferType() != null || standingInstructionDTO.clientId() != null
|
if (standingInstructionDTO.transferType() != null || standingInstructionDTO.clientId() != null
|
||||||
|| standingInstructionDTO.clientName() != null) {
|
|| standingInstructionDTO.clientName() != null
|
||||||
|
|| (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null)) {
|
||||||
sqlBuilder.append(" where ");
|
sqlBuilder.append(" where ");
|
||||||
}
|
}
|
||||||
boolean addAndCaluse = false;
|
boolean addAndCaluse = false;
|
||||||
@@ -301,6 +305,9 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
|
|||||||
} else if (accountType.isLoanAccount()) {
|
} else if (accountType.isLoanAccount()) {
|
||||||
sqlBuilder.append(" fromloanacc.id=? ");
|
sqlBuilder.append(" fromloanacc.id=? ");
|
||||||
paramObj.add(standingInstructionDTO.fromAccount());
|
paramObj.add(standingInstructionDTO.fromAccount());
|
||||||
|
} else if (accountType.isCurrentAccount()) {
|
||||||
|
sqlBuilder.append(" fromcuracc.id=? ");
|
||||||
|
paramObj.add(standingInstructionDTO.fromAccount());
|
||||||
}
|
}
|
||||||
addAndCaluse = true;
|
addAndCaluse = true;
|
||||||
}
|
}
|
||||||
@@ -387,7 +394,11 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
|
|||||||
sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,");
|
sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,");
|
||||||
sqlBuilder.append("tosp.id as toProductId, tosp.name as toProductName, ");
|
sqlBuilder.append("tosp.id as toProductId, tosp.name as toProductName, ");
|
||||||
sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo, ");
|
sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo, ");
|
||||||
sqlBuilder.append("tolp.id as toLoanProductId, tolp.name as toLoanProductName ");
|
sqlBuilder.append("tolp.id as toLoanProductId, tolp.name as toLoanProductName, ");
|
||||||
|
sqlBuilder.append("fromcuracc.id as fromCurrentAccountId, fromcuracc.account_no as fromCurrentAccountNo, ");
|
||||||
|
sqlBuilder.append("fromcp.id as fromCurrentProductId, fromcp.name as fromCurrentProductName, ");
|
||||||
|
sqlBuilder.append("tocuracc.id as toCurrentAccountId, tocuracc.account_no as toCurrentAccountNo, ");
|
||||||
|
sqlBuilder.append("tocp.id as toCurrentProductId, tocp.name as toCurrentProductName ");
|
||||||
sqlBuilder.append(" FROM m_account_transfer_standing_instructions atsi ");
|
sqlBuilder.append(" FROM m_account_transfer_standing_instructions atsi ");
|
||||||
sqlBuilder.append("join m_account_transfer_details atd on atd.id = atsi.account_transfer_details_id ");
|
sqlBuilder.append("join m_account_transfer_details atd on atd.id = atsi.account_transfer_details_id ");
|
||||||
sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id ");
|
sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id ");
|
||||||
@@ -402,6 +413,10 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
|
|||||||
sqlBuilder.append("left join m_savings_product tosp ON tosavacc.product_id = tosp.id ");
|
sqlBuilder.append("left join m_savings_product tosp ON tosavacc.product_id = tosp.id ");
|
||||||
sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id ");
|
sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id ");
|
||||||
sqlBuilder.append("left join m_product_loan tolp ON toloanacc.product_id = tolp.id ");
|
sqlBuilder.append("left join m_product_loan tolp ON toloanacc.product_id = tolp.id ");
|
||||||
|
sqlBuilder.append("left join m_current_account fromcuracc on fromcuracc.id = atd.from_current_account_id ");
|
||||||
|
sqlBuilder.append("left join m_current_product fromcp ON fromcuracc.product_id = fromcp.id ");
|
||||||
|
sqlBuilder.append("left join m_current_account tocuracc on tocuracc.id = atd.to_current_account_id ");
|
||||||
|
sqlBuilder.append("left join m_current_product tocp ON tocuracc.product_id = tocp.id ");
|
||||||
|
|
||||||
this.schemaSql = sqlBuilder.toString();
|
this.schemaSql = sqlBuilder.toString();
|
||||||
}
|
}
|
||||||
@@ -489,6 +504,16 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
|
|||||||
fromAccount = new PortfolioAccountData(fromLoanAccountId, fromLoanAccountNo, null, null, null, null, null,
|
fromAccount = new PortfolioAccountData(fromLoanAccountId, fromLoanAccountNo, null, null, null, null, null,
|
||||||
fromLoanProductId, fromLoanProductName, null, null, null);
|
fromLoanProductId, fromLoanProductName, null, null, null);
|
||||||
fromAccountType = accountType(PortfolioAccountType.LOAN);
|
fromAccountType = accountType(PortfolioAccountType.LOAN);
|
||||||
|
} else {
|
||||||
|
final Long fromCurrentAccountId = JdbcSupport.getLong(rs, "fromCurrentAccountId");
|
||||||
|
if (fromCurrentAccountId != null) {
|
||||||
|
final String fromCurrentAccountNo = rs.getString("fromCurrentAccountNo");
|
||||||
|
final Long fromCurrentProductId = JdbcSupport.getLong(rs, "fromCurrentProductId");
|
||||||
|
final String fromCurrentProductName = rs.getString("fromCurrentProductName");
|
||||||
|
fromAccount = new PortfolioAccountData(fromCurrentAccountId, fromCurrentAccountNo, null, null, null, null, null,
|
||||||
|
fromCurrentProductId, fromCurrentProductName, null, null, null);
|
||||||
|
fromAccountType = accountType(PortfolioAccountType.CURRENT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PortfolioAccountData toAccount = null;
|
PortfolioAccountData toAccount = null;
|
||||||
@@ -510,6 +535,16 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
|
|||||||
toAccount = new PortfolioAccountData(toLoanAccountId, toLoanAccountNo, null, null, null, null, null, toLoanProductId,
|
toAccount = new PortfolioAccountData(toLoanAccountId, toLoanAccountNo, null, null, null, null, null, toLoanProductId,
|
||||||
toLoanProductName, null, null, null);
|
toLoanProductName, null, null, null);
|
||||||
toAccountType = accountType(PortfolioAccountType.LOAN);
|
toAccountType = accountType(PortfolioAccountType.LOAN);
|
||||||
|
} else {
|
||||||
|
final Long toCurrentAccountId = JdbcSupport.getLong(rs, "toCurrentAccountId");
|
||||||
|
if (toCurrentAccountId != null) {
|
||||||
|
final String toCurrentAccountNo = rs.getString("toCurrentAccountNo");
|
||||||
|
final Long toCurrentProductId = JdbcSupport.getLong(rs, "toCurrentProductId");
|
||||||
|
final String toCurrentProductName = rs.getString("toCurrentProductName");
|
||||||
|
toAccount = new PortfolioAccountData(toCurrentAccountId, toCurrentAccountNo, null, null, null, null, null,
|
||||||
|
toCurrentProductId, toCurrentProductName, null, null, null);
|
||||||
|
toAccountType = accountType(PortfolioAccountType.CURRENT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return StandingInstructionData.instance(id, accountDetailId, name, fromOffice, toOffice, fromClient, toClient, fromAccountType,
|
return StandingInstructionData.instance(id, accountDetailId, name, fromOffice, toOffice, fromClient, toClient, fromAccountType,
|
||||||
|
|||||||
+36
-2
@@ -82,11 +82,25 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst
|
|||||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
||||||
} else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
} else if (isLoanToSavingsAccountTransfer(fromAccountType, toAccountType)) {
|
||||||
|
|
||||||
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleLoanToSavingsTransfer(command);
|
final AccountTransferDetails standingInstruction = this.standingInstructionAssembler.assembleLoanToSavingsTransfer(command);
|
||||||
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
this.accountTransferDetailRepository.saveAndFlush(standingInstruction);
|
||||||
standingInstructionId = standingInstruction.accountTransferStandingInstruction().getId();
|
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) {
|
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||||
final Throwable throwable = dve.getMostSpecificCause();
|
final Throwable throwable = dve.getMostSpecificCause();
|
||||||
@@ -122,6 +136,26 @@ public class StandingInstructionWritePlatformServiceImpl implements StandingInst
|
|||||||
return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount();
|
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
|
@Override
|
||||||
public CommandProcessingResult update(final Long id, final JsonCommand command) {
|
public CommandProcessingResult update(final Long id, final JsonCommand command) {
|
||||||
this.standingInstructionDataValidator.validateForUpdate(command);
|
this.standingInstructionDataValidator.validateForUpdate(command);
|
||||||
|
|||||||
+8
-2
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.account.starter;
|
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.configuration.domain.ConfigurationDomainService;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||||
@@ -26,6 +27,8 @@ import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecific
|
|||||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||||
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
|
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
|
||||||
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
|
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.AccountTransfersDataValidator;
|
||||||
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
import org.apache.fineract.portfolio.account.data.StandingInstructionDataValidator;
|
||||||
import org.apache.fineract.portfolio.account.domain.AccountTransferAssembler;
|
import org.apache.fineract.portfolio.account.domain.AccountTransferAssembler;
|
||||||
@@ -91,11 +94,14 @@ public class AccountConfiguration {
|
|||||||
LoanAccountDomainService loanAccountDomainService, SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
LoanAccountDomainService loanAccountDomainService, SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||||
AccountTransferDetailRepository accountTransferDetailRepository, LoanReadPlatformService loanReadPlatformService,
|
AccountTransferDetailRepository accountTransferDetailRepository, LoanReadPlatformService loanReadPlatformService,
|
||||||
GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory,
|
GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService, ExternalIdFactory externalIdFactory,
|
||||||
FineractProperties fineractProperties) {
|
FineractProperties fineractProperties, CurrentAccountRepository currentAccountRepository,
|
||||||
|
CurrentAccountTransactionRepository currentAccountTransactionRepository,
|
||||||
|
AccountingProcessorHelper accountingProcessorHelper) {
|
||||||
return new AccountTransfersWritePlatformServiceImpl(accountTransfersDataValidator, accountTransferAssembler,
|
return new AccountTransfersWritePlatformServiceImpl(accountTransfersDataValidator, accountTransferAssembler,
|
||||||
accountTransferRepository, savingsAccountAssembler, savingsAccountDomainService, loanAccountAssembler,
|
accountTransferRepository, savingsAccountAssembler, savingsAccountDomainService, loanAccountAssembler,
|
||||||
loanAccountDomainService, savingsAccountWritePlatformService, accountTransferDetailRepository, loanReadPlatformService,
|
loanAccountDomainService, savingsAccountWritePlatformService, accountTransferDetailRepository, loanReadPlatformService,
|
||||||
gsimRepository, configurationDomainService, externalIdFactory, fineractProperties);
|
gsimRepository, configurationDomainService, externalIdFactory, fineractProperties,
|
||||||
|
currentAccountRepository, currentAccountTransactionRepository, accountingProcessorHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
+30
-1
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.portfolio.accountdetails.data;
|
package org.apache.fineract.portfolio.accountdetails.data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
|
||||||
|
|
||||||
@@ -44,12 +45,28 @@ public class GuarantorAccountSummaryData {
|
|||||||
private final Boolean isActive;
|
private final Boolean isActive;
|
||||||
private final String relationship;
|
private final String relationship;
|
||||||
private final BigDecimal onHoldAmount;
|
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,
|
public GuarantorAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
|
||||||
final String loanProductName, final String shortLoanProductName, final LoanStatusEnumData loanStatus,
|
final String loanProductName, final String shortLoanProductName, final LoanStatusEnumData loanStatus,
|
||||||
final EnumOptionData loanType, final Integer loanCycle, final Boolean inArrears, final BigDecimal originalLoan,
|
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 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.id = id;
|
||||||
this.accountNo = accountNo;
|
this.accountNo = accountNo;
|
||||||
this.externalId = externalId;
|
this.externalId = externalId;
|
||||||
@@ -66,5 +83,17 @@ public class GuarantorAccountSummaryData {
|
|||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
this.relationship = relationship;
|
this.relationship = relationship;
|
||||||
this.onHoldAmount = onHoldAmount;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-9
@@ -725,7 +725,8 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
|||||||
accountsSummary.append(" l.product_id as productId, lp.name as productName, lp.short_name as shortProductName,")
|
accountsSummary.append(" l.product_id as productId, lp.name as productName, lp.short_name as shortProductName,")
|
||||||
.append(" l.loan_status_id as statusId, l.loan_type_enum as loanType,")
|
.append(" l.loan_status_id as statusId, l.loan_type_enum as loanType,")
|
||||||
|
|
||||||
.append("l.principal_disbursed_derived as originalLoan,").append("l.total_outstanding_derived as loanBalance,")
|
.append(" COALESCE(NULLIF(l.principal_disbursed_derived, 0), l.principal_amount) as originalLoan,")
|
||||||
|
.append("l.total_outstanding_derived as loanBalance,")
|
||||||
.append("l.total_repayment_derived as amountPaid,")
|
.append("l.total_repayment_derived as amountPaid,")
|
||||||
|
|
||||||
.append(" l.loan_product_counter as loanCycle,")
|
.append(" l.loan_product_counter as loanCycle,")
|
||||||
@@ -737,12 +738,25 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
|||||||
.append(" l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate,")
|
.append(" l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate,")
|
||||||
.append(" l.closedon_date as closedOnDate,").append(" la.overdue_since_date_derived as overdueSinceDate,")
|
.append(" l.closedon_date as closedOnDate,").append(" la.overdue_since_date_derived as overdueSinceDate,")
|
||||||
.append(" l.writtenoffon_date as writtenOffOnDate, l.maturedon_date as actualMaturityDate, l.expected_maturedon_date as expectedMaturityDate,")
|
.append(" l.writtenoffon_date as writtenOffOnDate, l.maturedon_date as actualMaturityDate, l.expected_maturedon_date as expectedMaturityDate,")
|
||||||
.append(" g.is_active as isActive,").append(" cv.code_value as relationship,").append(" sa.on_hold_funds_derived")
|
.append(" g.is_active as isActive,").append(" cv.code_value as relationship,")
|
||||||
|
.append(" gfd.amount as on_hold_funds_derived,")
|
||||||
|
.append(" sa.account_no as savingsAccountNo,")
|
||||||
|
.append(" sa.account_balance_derived as savingsBalance,")
|
||||||
|
.append(" gfd.amount_released_derived as amountReleased,")
|
||||||
|
.append(" gfd.amount_remaining_derived as amountRemaining,")
|
||||||
|
.append(" bc.display_name as borrowerName,")
|
||||||
|
.append(" COALESCE(la.total_overdue_derived, 0) as totalOverdue,")
|
||||||
|
.append(" (SELECT COUNT(*) FROM m_loan_repayment_schedule mr2")
|
||||||
|
.append(" WHERE mr2.loan_id = l.id AND mr2.completed_derived = false AND mr2.duedate < CURRENT_DATE) as missedInstallments")
|
||||||
.append(" from m_loan l ").append(" join m_guarantor as g on g.loan_id = l.id ")
|
.append(" from m_loan l ").append(" join m_guarantor as g on g.loan_id = l.id ")
|
||||||
.append(" join m_client as c on c.id = g.entity_id ").append(" LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id")
|
.append(" join m_client as c on c.id = g.entity_id ")
|
||||||
|
.append(" join m_client as bc on bc.id = l.client_id ")
|
||||||
|
.append(" LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id")
|
||||||
.append(" left join m_loan_arrears_aging la on la.loan_id = l.id")
|
.append(" left join m_loan_arrears_aging la on la.loan_id = l.id")
|
||||||
.append(" left join m_code_value cv ON cv.id = g.client_reln_cv_id")
|
.append(" left join m_code_value cv ON cv.id = g.client_reln_cv_id")
|
||||||
.append(" left join m_savings_account sa on sa.client_id = c.id");
|
.append(" left join m_guarantor_funding_details gfd on gfd.guarantor_id = g.id")
|
||||||
|
.append(" left join m_portfolio_account_associations paa on paa.id = gfd.account_associations_id")
|
||||||
|
.append(" left join m_savings_account sa on sa.id = paa.linked_savings_account_id");
|
||||||
|
|
||||||
return accountsSummary.toString();
|
return accountsSummary.toString();
|
||||||
}
|
}
|
||||||
@@ -768,16 +782,29 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
|||||||
final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "on_hold_funds_derived");
|
final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "on_hold_funds_derived");
|
||||||
|
|
||||||
final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate");
|
final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate");
|
||||||
Boolean inArrears = true;
|
final Integer missedInstallments = JdbcSupport.getInteger(rs, "missedInstallments");
|
||||||
if (overdueSinceDate == null) {
|
// inArrears: true if batch job has populated overdue_since_date OR if
|
||||||
inArrears = false;
|
// schedule has unpaid past-due installments (batch job may not have run yet)
|
||||||
}
|
final Boolean inArrears = overdueSinceDate != null || (missedInstallments != null && missedInstallments > 0);
|
||||||
|
|
||||||
final Boolean isActive = rs.getBoolean("isActive");
|
final Boolean isActive = rs.getBoolean("isActive");
|
||||||
|
|
||||||
final String relationship = rs.getString("relationship");
|
final String relationship = rs.getString("relationship");
|
||||||
|
final String savingsAccountNo = rs.getString("savingsAccountNo");
|
||||||
|
final BigDecimal savingsBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "savingsBalance");
|
||||||
|
final BigDecimal amountReleased = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amountReleased");
|
||||||
|
final BigDecimal amountRemaining = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "amountRemaining");
|
||||||
|
final String borrowerName = rs.getString("borrowerName");
|
||||||
|
final BigDecimal totalOverdue = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalOverdue");
|
||||||
|
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
|
||||||
|
final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
|
||||||
|
final LocalDate disbursedOnDate = JdbcSupport.getLocalDate(rs, "actualDisbursementDate");
|
||||||
|
final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate");
|
||||||
return new GuarantorAccountSummaryData(id, accountNo, externalId, productId, loanProductName, shortLoanProductName, loanStatus,
|
return new GuarantorAccountSummaryData(id, accountNo, externalId, productId, loanProductName, shortLoanProductName, loanStatus,
|
||||||
loanType, loanCycle, inArrears, originalLoan, loanBalance, amountPaid, isActive, relationship, onHoldAmount);
|
loanType, loanCycle, inArrears, originalLoan, loanBalance, amountPaid, isActive, relationship, onHoldAmount,
|
||||||
|
savingsAccountNo, amountReleased, borrowerName, totalOverdue, overdueSinceDate,
|
||||||
|
submittedOnDate, approvedOnDate, disbursedOnDate, expectedMaturityDate, amountRemaining, savingsBalance,
|
||||||
|
missedInstallments);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+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;
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package org.apache.fineract.portfolio.cashexchange.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CashExchangeDetailRepository extends JpaRepository<CashExchangeDetail, Long> {
|
||||||
|
List<CashExchangeDetail> findByExchangeId(Long exchangeId);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user