fix(cashier): fix GL account search SQL bug, running balance, and add CASHIER role permissions

- Fix searchParam SQL syntax: parameterized LIKE with %value% instead of literal %?%
- Fix organizationRunningBalance: correlated subquery for latest journal entry (ORDER BY id DESC LIMIT 1)
- Migration 0317: grant READ_GLACCOUNT, READ_LOAN, READ_SHAREACCOUNT to CASHIER role

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-10 11:17:32 +05:30
co-authored by Claude Sonnet 4.6
parent 6d0c1fe7f6
commit 53b6a94bf8
3 changed files with 325 additions and 272 deletions
@@ -1,272 +1,275 @@
/** /**
* Licensed to the Apache Software Foundation (ASF) under one * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file * or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information * distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file * regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the * to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance * "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, * Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an * software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the * KIND, either express or implied. See the License for the
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.fineract.accounting.glaccount.service; package org.apache.fineract.accounting.glaccount.service;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.common.AccountingEnumerations; import org.apache.fineract.accounting.common.AccountingEnumerations;
import org.apache.fineract.accounting.glaccount.data.GLAccountData; import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup; import org.apache.fineract.accounting.glaccount.data.GLAccountDataForLookup;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType; import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage; import org.apache.fineract.accounting.glaccount.domain.GLAccountUsage;
import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidClassificationException; import org.apache.fineract.accounting.glaccount.exception.GLAccountInvalidClassificationException;
import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException; import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundException;
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData; import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformService { public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformService {
private static final String NAME_DECORATED_BASE_ON_HIERARCHY = "concat(substring('........................................', 1, ((LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '.', '')) - 1) * 4)), name)"; private static final String NAME_DECORATED_BASE_ON_HIERARCHY = "concat(substring('........................................', 1, ((LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '.', '')) - 1) * 4)), name)";
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private static final String SQL_AND = " and "; private static final String SQL_AND = " and ";
private static final class GLAccountMapper implements RowMapper<GLAccountData> { private static final class GLAccountMapper implements RowMapper<GLAccountData> {
private final JournalEntryAssociationParametersData associationParametersData; private final JournalEntryAssociationParametersData associationParametersData;
GLAccountMapper(final JournalEntryAssociationParametersData associationParametersData) { GLAccountMapper(final JournalEntryAssociationParametersData associationParametersData) {
if (associationParametersData == null) { if (associationParametersData == null) {
this.associationParametersData = new JournalEntryAssociationParametersData(); this.associationParametersData = new JournalEntryAssociationParametersData();
} else { } else {
this.associationParametersData = associationParametersData; this.associationParametersData = associationParametersData;
} }
} }
public String schema() { public String schema() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( sb.append(
" gl.id as id, name as name, parent_id as parentId, gl_code as glCode, disabled as disabled, manual_journal_entries_allowed as manualEntriesAllowed, ") " gl.id as id, name as name, parent_id as parentId, gl_code as glCode, disabled as disabled, manual_journal_entries_allowed as manualEntriesAllowed, ")
.append("classification_enum as classification, account_usage as accountUsage, gl.description as description, ") .append("classification_enum as classification, account_usage as accountUsage, gl.description as description, ")
.append(NAME_DECORATED_BASE_ON_HIERARCHY).append(" as nameDecorated, ") .append(NAME_DECORATED_BASE_ON_HIERARCHY).append(" as nameDecorated, ")
.append("cv.id as codeId, cv.code_value as codeValue "); .append("cv.id as codeId, cv.code_value as codeValue ");
if (this.associationParametersData.isRunningBalanceRequired()) { if (this.associationParametersData.isRunningBalanceRequired()) {
sb.append(",gl_j.organization_running_balance as organizationRunningBalance "); sb.append(",gl_j.organization_running_balance as organizationRunningBalance ");
} }
sb.append("from acc_gl_account gl left join m_code_value cv on tag_id=cv.id "); sb.append("from acc_gl_account gl left join m_code_value cv on tag_id=cv.id ");
if (this.associationParametersData.isRunningBalanceRequired()) { if (this.associationParametersData.isRunningBalanceRequired()) {
sb.append("left outer Join acc_gl_journal_entry gl_j on gl_j.account_id = gl.id"); sb.append("left outer join acc_gl_journal_entry gl_j on gl_j.id = (")
} .append("select id from acc_gl_journal_entry ")
return sb.toString(); .append("where account_id = gl.id ")
} .append("order by id desc limit 1) ");
}
@Override return sb.toString();
public GLAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { }
final Long id = rs.getLong("id"); @Override
final String name = rs.getString("name"); public GLAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long parentId = JdbcSupport.getLong(rs, "parentId");
final String glCode = rs.getString("glCode"); final Long id = rs.getLong("id");
final boolean disabled = rs.getBoolean("disabled"); final String name = rs.getString("name");
final boolean manualEntriesAllowed = rs.getBoolean("manualEntriesAllowed"); final Long parentId = JdbcSupport.getLong(rs, "parentId");
final int accountTypeId = JdbcSupport.getInteger(rs, "classification"); final String glCode = rs.getString("glCode");
final EnumOptionData accountType = AccountingEnumerations.gLAccountType(accountTypeId); final boolean disabled = rs.getBoolean("disabled");
final int usageId = JdbcSupport.getInteger(rs, "accountUsage"); final boolean manualEntriesAllowed = rs.getBoolean("manualEntriesAllowed");
final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(usageId); final int accountTypeId = JdbcSupport.getInteger(rs, "classification");
final String description = rs.getString("description"); final EnumOptionData accountType = AccountingEnumerations.gLAccountType(accountTypeId);
final String nameDecorated = rs.getString("nameDecorated"); final int usageId = JdbcSupport.getInteger(rs, "accountUsage");
final Long codeId = rs.wasNull() ? null : rs.getLong("codeId"); final EnumOptionData usage = AccountingEnumerations.gLAccountUsage(usageId);
final String codeValue = rs.getString("codeValue"); final String description = rs.getString("description");
final CodeValueData tagId = CodeValueData.instance(codeId, codeValue); final String nameDecorated = rs.getString("nameDecorated");
Long organizationRunningBalance = null; final Long codeId = rs.wasNull() ? null : rs.getLong("codeId");
if (associationParametersData.isRunningBalanceRequired()) { final String codeValue = rs.getString("codeValue");
organizationRunningBalance = rs.getLong("organizationRunningBalance"); final CodeValueData tagId = CodeValueData.instance(codeId, codeValue);
} Long organizationRunningBalance = null;
return new GLAccountData().setId(id).setName(name).setParentId(parentId).setGlCode(glCode).setDisabled(disabled) if (associationParametersData.isRunningBalanceRequired()) {
.setManualEntriesAllowed(manualEntriesAllowed).setType(accountType).setUsage(usage).setDescription(description) organizationRunningBalance = rs.getLong("organizationRunningBalance");
.setNameDecorated(nameDecorated).setTagId(tagId).setOrganizationRunningBalance(organizationRunningBalance); }
} return new GLAccountData().setId(id).setName(name).setParentId(parentId).setGlCode(glCode).setDisabled(disabled)
} .setManualEntriesAllowed(manualEntriesAllowed).setType(accountType).setUsage(usage).setDescription(description)
.setNameDecorated(nameDecorated).setTagId(tagId).setOrganizationRunningBalance(organizationRunningBalance);
@Override }
public List<GLAccountData> retrieveAllGLAccounts(final Integer accountClassification, final String searchParam, final Integer usage, }
final Boolean manualTransactionsAllowed, final Boolean disabled,
JournalEntryAssociationParametersData associationParametersData) { @Override
if (accountClassification != null && !checkValidGLAccountType(accountClassification)) { public List<GLAccountData> retrieveAllGLAccounts(final Integer accountClassification, final String searchParam, final Integer usage,
throw new GLAccountInvalidClassificationException(accountClassification); final Boolean manualTransactionsAllowed, final Boolean disabled,
} JournalEntryAssociationParametersData associationParametersData) {
if (accountClassification != null && !checkValidGLAccountType(accountClassification)) {
if (usage != null && !checkValidGLAccountUsage(usage)) { throw new GLAccountInvalidClassificationException(accountClassification);
throw new GLAccountInvalidClassificationException(accountClassification); }
}
if (usage != null && !checkValidGLAccountUsage(usage)) {
final GLAccountMapper rm = new GLAccountMapper(associationParametersData); throw new GLAccountInvalidClassificationException(accountClassification);
String sql = "select " + rm.schema(); }
// append SQL statement for fetching account totals
if (associationParametersData != null && associationParametersData.isRunningBalanceRequired()) { final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
sql = sql + " and gl_j.id in (select t1.id from (select t2.account_id, max(t2.id) as id from " String sql = "select " + rm.schema();
+ "(select id, max(entry_date) as entry_date, account_id from acc_gl_journal_entry where is_running_balance_calculated = true " // append SQL statement for fetching account totals
+ "group by account_id desc, id) t3 inner join acc_gl_journal_entry t2 on t2.account_id = t3.account_id and t2.entry_date = t3.entry_date " if (associationParametersData != null && associationParametersData.isRunningBalanceRequired()) {
+ "group by t2.account_id desc) t1)"; sql = sql + " and gl_j.id in (select t1.id from (select t2.account_id, max(t2.id) as id from "
} + "(select id, max(entry_date) as entry_date, account_id from acc_gl_journal_entry where is_running_balance_calculated = true "
final Object[] parameterArray = new Object[3]; + "group by account_id desc, id) t3 inner join acc_gl_journal_entry t2 on t2.account_id = t3.account_id and t2.entry_date = t3.entry_date "
int arrayPos = 0; + "group by t2.account_id desc) t1)";
boolean filtersPresent = false; }
if ((accountClassification != null) || StringUtils.isNotBlank(searchParam) || (usage != null) || (manualTransactionsAllowed != null) final Object[] parameterArray = new Object[3];
|| (disabled != null)) { int arrayPos = 0;
filtersPresent = true; boolean filtersPresent = false;
sql += " where"; if ((accountClassification != null) || StringUtils.isNotBlank(searchParam) || (usage != null) || (manualTransactionsAllowed != null)
} || (disabled != null)) {
filtersPresent = true;
if (filtersPresent) { sql += " where";
boolean firstWhereConditionAdded = false; }
if (accountClassification != null) {
sql += " classification_enum = ?"; if (filtersPresent) {
parameterArray[arrayPos] = accountClassification.shortValue(); boolean firstWhereConditionAdded = false;
arrayPos = arrayPos + 1; if (accountClassification != null) {
firstWhereConditionAdded = true; sql += " classification_enum = ?";
} parameterArray[arrayPos] = accountClassification.shortValue();
if (StringUtils.isNotBlank(searchParam)) { arrayPos = arrayPos + 1;
if (firstWhereConditionAdded) { firstWhereConditionAdded = true;
sql += SQL_AND; }
} if (StringUtils.isNotBlank(searchParam)) {
sql += " ( name like %?% or gl_code like %?% )"; if (firstWhereConditionAdded) {
parameterArray[arrayPos] = searchParam; sql += SQL_AND;
arrayPos = arrayPos + 1; }
parameterArray[arrayPos] = searchParam; sql += " ( name like ? or gl_code like ? )";
arrayPos = arrayPos + 1; parameterArray[arrayPos] = "%" + searchParam + "%";
firstWhereConditionAdded = true; arrayPos = arrayPos + 1;
} parameterArray[arrayPos] = "%" + searchParam + "%";
if (usage != null) { arrayPos = arrayPos + 1;
if (firstWhereConditionAdded) { firstWhereConditionAdded = true;
sql += SQL_AND; }
} if (usage != null) {
if (GLAccountUsage.HEADER.getValue().equals(usage)) { if (firstWhereConditionAdded) {
sql += " account_usage = 2 "; sql += SQL_AND;
} else if (GLAccountUsage.DETAIL.getValue().equals(usage)) { }
sql += " account_usage = 1 "; if (GLAccountUsage.HEADER.getValue().equals(usage)) {
} sql += " account_usage = 2 ";
firstWhereConditionAdded = true; } else if (GLAccountUsage.DETAIL.getValue().equals(usage)) {
} sql += " account_usage = 1 ";
if (manualTransactionsAllowed != null) { }
if (firstWhereConditionAdded) { firstWhereConditionAdded = true;
sql += SQL_AND; }
} if (manualTransactionsAllowed != null) {
if (firstWhereConditionAdded) {
sql += " manual_journal_entries_allowed = " + manualTransactionsAllowed; sql += SQL_AND;
firstWhereConditionAdded = true; }
}
if (disabled != null) { sql += " manual_journal_entries_allowed = " + manualTransactionsAllowed;
if (firstWhereConditionAdded) { firstWhereConditionAdded = true;
sql += SQL_AND; }
} if (disabled != null) {
if (firstWhereConditionAdded) {
sql += " disabled = " + disabled; sql += SQL_AND;
} }
}
sql += " disabled = " + disabled;
sql += " ORDER BY gl_code ASC"; }
}
final Object[] finalObjectArray = Arrays.copyOf(parameterArray, arrayPos);
return this.jdbcTemplate.query(sql, rm, (Object[]) finalObjectArray);// NOSONAR sql += " ORDER BY gl_code ASC";
}
final Object[] finalObjectArray = Arrays.copyOf(parameterArray, arrayPos);
@Override return this.jdbcTemplate.query(sql, rm, (Object[]) finalObjectArray);// NOSONAR
public GLAccountData retrieveGLAccountById(final long glAccountId, JournalEntryAssociationParametersData associationParametersData) { }
try {
final GLAccountMapper rm = new GLAccountMapper(associationParametersData); @Override
final StringBuilder sql = new StringBuilder(); public GLAccountData retrieveGLAccountById(final long glAccountId, JournalEntryAssociationParametersData associationParametersData) {
sql.append("select ").append(rm.schema()); try {
sql.append(" where gl.id = ?"); final GLAccountMapper rm = new GLAccountMapper(associationParametersData);
if (associationParametersData.isRunningBalanceRequired()) { final StringBuilder sql = new StringBuilder();
sql.append(" and gl_j.is_running_balance_calculated = true ") sql.append("select ").append(rm.schema());
.append(" ORDER BY gl_j.entry_date DESC,gl_j.id DESC LIMIT 1"); sql.append(" where gl.id = ?");
} if (associationParametersData.isRunningBalanceRequired()) {
sql.append(" and gl_j.is_running_balance_calculated = true ")
return this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId }); .append(" ORDER BY gl_j.entry_date DESC,gl_j.id DESC LIMIT 1");
} catch (final EmptyResultDataAccessException e) { }
throw new GLAccountNotFoundException(glAccountId, e);
} return this.jdbcTemplate.queryForObject(sql.toString(), rm, new Object[] { glAccountId });
} } catch (final EmptyResultDataAccessException e) {
throw new GLAccountNotFoundException(glAccountId, e);
@Override }
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts(final GLAccountType accountType) { }
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.DETAIL.getValue(), null, false,
new JournalEntryAssociationParametersData()); @Override
} public List<GLAccountData> retrieveAllEnabledDetailGLAccounts(final GLAccountType accountType) {
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.DETAIL.getValue(), null, false,
@Override new JournalEntryAssociationParametersData());
public List<GLAccountData> retrieveAllEnabledDetailGLAccounts() { }
return retrieveAllGLAccounts(null, null, GLAccountUsage.DETAIL.getValue(), null, false,
new JournalEntryAssociationParametersData()); @Override
} public List<GLAccountData> retrieveAllEnabledDetailGLAccounts() {
return retrieveAllGLAccounts(null, null, GLAccountUsage.DETAIL.getValue(), null, false,
private static boolean checkValidGLAccountType(final int type) { new JournalEntryAssociationParametersData());
for (final GLAccountType accountType : GLAccountType.values()) { }
if (accountType.getValue().equals(type)) {
return true; private static boolean checkValidGLAccountType(final int type) {
} for (final GLAccountType accountType : GLAccountType.values()) {
} if (accountType.getValue().equals(type)) {
return false; return true;
} }
}
private static boolean checkValidGLAccountUsage(final int type) { return false;
for (final GLAccountUsage accountUsage : GLAccountUsage.values()) { }
if (accountUsage.getValue().equals(type)) {
return true; private static boolean checkValidGLAccountUsage(final int type) {
} for (final GLAccountUsage accountUsage : GLAccountUsage.values()) {
} if (accountUsage.getValue().equals(type)) {
return false; return true;
} }
}
@Override return false;
public GLAccountData retrieveNewGLAccountDetails(final Integer type) { }
return GLAccountData.sensibleDefaultsForNewGLAccountCreation(type);
} @Override
public GLAccountData retrieveNewGLAccountDetails(final Integer type) {
@Override return GLAccountData.sensibleDefaultsForNewGLAccountCreation(type);
public List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(final GLAccountType accountType) { }
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.HEADER.getValue(), null, false,
new JournalEntryAssociationParametersData()); @Override
} public List<GLAccountData> retrieveAllEnabledHeaderGLAccounts(final GLAccountType accountType) {
return retrieveAllGLAccounts(accountType.getValue(), null, GLAccountUsage.HEADER.getValue(), null, false,
@Override new JournalEntryAssociationParametersData());
public List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType) { }
final GLAccountDataLookUpMapper mapper = new GLAccountDataLookUpMapper();
final String sql = "Select " + GLAccountDataLookUpMapper.LOOKUP_SCHEMA + " where rule.id=? and tags.acc_type_enum=?"; @Override
return this.jdbcTemplate.query(sql, mapper, (Object[]) new Object[] { ruleId, transactionType });// NOSONAR public List<GLAccountDataForLookup> retrieveAccountsByTagId(final Long ruleId, final Integer transactionType) {
} final GLAccountDataLookUpMapper mapper = new GLAccountDataLookUpMapper();
final String sql = "Select " + GLAccountDataLookUpMapper.LOOKUP_SCHEMA + " where rule.id=? and tags.acc_type_enum=?";
private static final class GLAccountDataLookUpMapper implements RowMapper<GLAccountDataForLookup> { return this.jdbcTemplate.query(sql, mapper, (Object[]) new Object[] { ruleId, transactionType });// NOSONAR
}
private static final String LOOKUP_SCHEMA = " gl.id as id, gl.name as name, gl.gl_code as glCode from acc_accounting_rule rule join acc_rule_tags tags on tags.acc_rule_id = rule.id join acc_gl_account gl on gl.tag_id=tags.tag_id";
private static final class GLAccountDataLookUpMapper implements RowMapper<GLAccountDataForLookup> {
@Override
public GLAccountDataForLookup mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { private static final String LOOKUP_SCHEMA = " gl.id as id, gl.name as name, gl.gl_code as glCode from acc_accounting_rule rule join acc_rule_tags tags on tags.acc_rule_id = rule.id join acc_gl_account gl on gl.tag_id=tags.tag_id";
final Long id = JdbcSupport.getLong(rs, "id");
final String name = rs.getString("name"); @Override
final String glCode = rs.getString("glCode"); public GLAccountDataForLookup mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = JdbcSupport.getLong(rs, "id");
return new GLAccountDataForLookup().setId(id).setName(name).setGlCode(glCode); final String name = rs.getString("name");
} final String glCode = rs.getString("glCode");
}
} return new GLAccountDataForLookup().setId(id).setName(name).setGlCode(glCode);
}
}
}
@@ -344,4 +344,5 @@
<include file="parts/0314_cashier_staff_unique_constraint.xml" relativeToChangelogFile="true"/> <include file="parts/0314_cashier_staff_unique_constraint.xml" relativeToChangelogFile="true"/>
<include file="parts/0315_grant_cashier_transaction_entry_permissions.xml" relativeToChangelogFile="true"/> <include file="parts/0315_grant_cashier_transaction_entry_permissions.xml" relativeToChangelogFile="true"/>
<include file="parts/0316_create_clerk_role_and_permissions.xml" relativeToChangelogFile="true"/> <include file="parts/0316_create_clerk_role_and_permissions.xml" relativeToChangelogFile="true"/>
<include file="parts/0317_grant_cashier_gl_loan_share_read_permissions.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
for licensing details.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<!--
Grant READ permissions to CASHIER role for new account types added
in the Cashier Transaction Entry screen:
- READ_GLACCOUNT : GL account lookup and balance (AC. TP. = GL)
- READ_LOAN : Loan account lookup (AC. TP. = LA)
- READ_SHAREACCOUNT : Share account lookup (AC. TP. = SHR)
-->
<changeSet id="0317_grant_cashier_gl_loan_share_read_permissions" author="fineract">
<sql>
INSERT INTO m_role_permission (role_id, permission_id)
SELECT r.id, p.id
FROM m_role r
JOIN m_permission p ON p.code IN (
'READ_GLACCOUNT',
'READ_LOAN',
'READ_SHAREACCOUNT'
)
WHERE r.name = 'CASHIER'
AND NOT EXISTS (
SELECT 1 FROM m_role_permission rp
WHERE rp.role_id = r.id AND rp.permission_id = p.id
);
</sql>
<rollback>
DELETE FROM m_role_permission
WHERE role_id = (SELECT id FROM m_role WHERE name = 'CASHIER')
AND permission_id IN (
SELECT id FROM m_permission
WHERE code IN (
'READ_GLACCOUNT',
'READ_LOAN',
'READ_SHAREACCOUNT'
)
);
</rollback>
</changeSet>
</databaseChangeLog>