feat(cashier): add role-based data filtering and pagination for teller workstation
- Add CashierRoleHelper service: detects CASHIER role via DB permission
check, resolves cashier ID from staff/user linkage
- Role-based filtering on GET list endpoints (cashtransfer, cashsettlement,
cashintercashier, cashexchange, cashreports): CASHIER sees only own data,
ADMIN/HEAD sees all
- Pagination (offset/limit) added to all cashier list APIs returning
standard Fineract Page format {totalFilteredRecords, pageItems}
- ShareCollectionApiResource: collect cash before account activation,
post GL entries (Dr Cash Vault / Cr Share Sundry / Cr Income from Fees),
mark charges paid to prevent duplicate GL on activation,
GET status endpoint for collection check
- Migration 0315: grant READ_CLIENTKYC, READ_CLIENTNOMINEE, READ_ADDRESS,
READ_FAMILYMEMBERS, READ_SAVINGSACCOUNT to CASHIER role
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
9d944d0196
commit
2371ad98b1
+25
-3
@@ -23,6 +23,7 @@ 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;
|
||||
@@ -45,6 +46,7 @@ public class CashExchangeApiResource {
|
||||
private final PlatformSecurityContext context;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
||||
private final CashierRoleHelper cashierRoleHelper;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
@@ -174,10 +176,30 @@ public class CashExchangeApiResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll(@QueryParam("accountId") Long accountId) {
|
||||
List<CashExchange> list = accountId != null ? repository.findByAccountIdOrderByCreatedAtDesc(accountId)
|
||||
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();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
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
|
||||
|
||||
+25
-3
@@ -11,6 +11,7 @@ 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;
|
||||
@@ -22,6 +23,7 @@ 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.cashintercashier.domain.CashInterCashier;
|
||||
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierDetail;
|
||||
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierDetailRepository;
|
||||
@@ -40,6 +42,7 @@ public class CashInterCashierApiResource {
|
||||
private final CashInterCashierRepository repository;
|
||||
private final CashInterCashierDetailRepository detailRepository;
|
||||
private final PlatformSecurityContext context;
|
||||
private final CashierRoleHelper cashierRoleHelper;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
@@ -143,9 +146,28 @@ public class CashInterCashierApiResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll() {
|
||||
return gson.toJson(repository.findAllByOrderByCreatedAtDesc().stream()
|
||||
.map(this::toMap).collect(Collectors.toList()));
|
||||
public String getAll(
|
||||
@QueryParam("offset") @jakarta.ws.rs.DefaultValue("0") int offset,
|
||||
@QueryParam("limit") @jakarta.ws.rs.DefaultValue("20") int limit) {
|
||||
final Long scopedCashierId = cashierRoleHelper.getCashierIdForFiltering();
|
||||
List<CashInterCashier> all = repository.findAllByOrderByCreatedAtDesc();
|
||||
if (scopedCashierId != null) {
|
||||
all = all.stream()
|
||||
.filter(t -> scopedCashierId.equals(t.getFromCashierId())
|
||||
|| scopedCashierId.equals(t.getToCashierId()))
|
||||
.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
|
||||
|
||||
+30
-3
@@ -14,6 +14,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashreport.service.CashierRoleHelper;
|
||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -27,6 +28,7 @@ public class CashReportApiResource {
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final PlatformSecurityContext context;
|
||||
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
||||
private final CashierRoleHelper cashierRoleHelper;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@GET
|
||||
@@ -115,10 +117,15 @@ public class CashReportApiResource {
|
||||
|
||||
@GET
|
||||
@Path("excess")
|
||||
public String excess(@QueryParam("threshold") BigDecimal threshold, @QueryParam("fromDate") String fromDate,
|
||||
@QueryParam("toDate") String toDate) {
|
||||
public String excess(@QueryParam("threshold") BigDecimal threshold,
|
||||
@QueryParam("fromDate") String fromDate,
|
||||
@QueryParam("toDate") String toDate,
|
||||
@jakarta.ws.rs.DefaultValue("0") @QueryParam("offset") int offset,
|
||||
@jakarta.ws.rs.DefaultValue("20") @QueryParam("limit") int limit) {
|
||||
final BigDecimal effectiveThreshold = threshold != null ? threshold : BigDecimal.valueOf(50000);
|
||||
final boolean hasDateRange = fromDate != null && !fromDate.isBlank() && toDate != null && !toDate.isBlank();
|
||||
// CASHIER role: scope to this cashier's transactions only
|
||||
final Long scopedCashierId = cashierRoleHelper.getCashierIdForFiltering();
|
||||
|
||||
// Savings / Current / FD / RD transactions
|
||||
final StringBuilder sql = new StringBuilder();
|
||||
@@ -154,6 +161,10 @@ public class CashReportApiResource {
|
||||
exSql.append("WHERE (is_reversed IS NULL OR is_reversed = false) AND exchange_amount >= ? ");
|
||||
final List<Object> exArgs = new ArrayList<>();
|
||||
exArgs.add(effectiveThreshold);
|
||||
if (scopedCashierId != null) {
|
||||
exSql.append("AND cashier_id = ? ");
|
||||
exArgs.add(scopedCashierId);
|
||||
}
|
||||
if (hasDateRange) {
|
||||
exSql.append("AND exchange_date BETWEEN CAST(? AS DATE) AND CAST(? AS DATE) ");
|
||||
exArgs.add(fromDate);
|
||||
@@ -181,7 +192,17 @@ public class CashReportApiResource {
|
||||
combined.addAll(exchangeRows);
|
||||
combined.sort((a, b) -> ((BigDecimal) b.get("totalAmount")).compareTo((BigDecimal) a.get("totalAmount")));
|
||||
|
||||
return gson.toJson(combined);
|
||||
final int total = combined.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 = combined.subList(safeOffset, safeTo);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,6 +297,12 @@ public class CashReportApiResource {
|
||||
@QueryParam("asOfDate") String asOfDate) {
|
||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
||||
|
||||
// CASHIER role: always scope to their own position only
|
||||
final Long scopedId = cashierRoleHelper.getCashierIdForFiltering();
|
||||
if (scopedId != null) {
|
||||
return gson.toJson(buildPositionForCashier(scopedId, dateFilter));
|
||||
}
|
||||
|
||||
if (cashierId != null) {
|
||||
return gson.toJson(buildPositionForCashier(cashierId, dateFilter));
|
||||
}
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package org.apache.fineract.portfolio.cashreport.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Helper service for cashier role detection and cashier ID resolution.
|
||||
*
|
||||
* Regular CASHIER role → isCashierOnly() = true → data scoped to that cashier
|
||||
* HEAD CASHIER / Admin → isCashierOnly() = false → all data visible
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CashierRoleHelper {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* Returns true ONLY for users whose SOLE meaningful role is "CASHIER".
|
||||
* Users with ALL_FUNCTIONS permission or a "head cashier" role return false.
|
||||
*/
|
||||
public boolean isCashierOnly() {
|
||||
try {
|
||||
AppUser user = context.authenticatedUser();
|
||||
|
||||
// Check via DB: if user's roles have ALL_FUNCTIONS permission → super-admin, not cashier-only
|
||||
Long allFunctionsCount = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_appuser_role aur "
|
||||
+ "JOIN m_role_permission rp ON rp.role_id = aur.role_id "
|
||||
+ "JOIN m_permission p ON p.id = rp.permission_id "
|
||||
+ "WHERE aur.appuser_id = ? AND p.code = 'ALL_FUNCTIONS'",
|
||||
Long.class, user.getId());
|
||||
if (allFunctionsCount != null && allFunctionsCount > 0) return false;
|
||||
|
||||
// Check role name is exactly "CASHIER" (case-insensitive)
|
||||
return user.getRoles().stream().anyMatch(r -> {
|
||||
String name = r.getName().toLowerCase().trim();
|
||||
return name.equals("cashier");
|
||||
});
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the m_cashiers.id for the currently authenticated user,
|
||||
* or null if the user has no associated cashier record.
|
||||
*/
|
||||
public Long getCurrentCashierId() {
|
||||
try {
|
||||
Long userId = context.authenticatedUser().getId();
|
||||
return 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 = ? ORDER BY mc.id DESC LIMIT 1",
|
||||
Long.class, userId);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: returns the cashier ID only when the user is cashier-only.
|
||||
* Returns null for admin/head-cashier users (no filtering needed).
|
||||
*/
|
||||
public Long getCashierIdForFiltering() {
|
||||
return isCashierOnly() ? getCurrentCashierId() : null;
|
||||
}
|
||||
}
|
||||
+23
-3
@@ -23,6 +23,7 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.portfolio.cashreport.service.CashierRoleHelper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlement;
|
||||
@@ -49,6 +50,7 @@ public class CashSettlementApiResource {
|
||||
private final GlDispatchService glDispatchService;
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final CashierRoleHelper cashierRoleHelper;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
@@ -224,9 +226,27 @@ public class CashSettlementApiResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll() {
|
||||
List<CashSettlement> list = repository.findAllByOrderByCreatedAtDesc();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
public String getAll(
|
||||
@QueryParam("offset") @jakarta.ws.rs.DefaultValue("0") int offset,
|
||||
@QueryParam("limit") @jakarta.ws.rs.DefaultValue("20") int limit) {
|
||||
final Long scopedCashierId = cashierRoleHelper.getCashierIdForFiltering();
|
||||
List<CashSettlement> all = repository.findAllByOrderByCreatedAtDesc();
|
||||
if (scopedCashierId != null) {
|
||||
all = all.stream()
|
||||
.filter(s -> scopedCashierId.equals(s.getFromCashierId()))
|
||||
.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
|
||||
|
||||
+24
-3
@@ -11,6 +11,7 @@ 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;
|
||||
@@ -22,6 +23,7 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.portfolio.cashreport.service.CashierRoleHelper;
|
||||
import org.apache.fineract.portfolio.teller.service.GlDispatchService;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashtransfer.domain.CashTransfer;
|
||||
@@ -48,6 +50,7 @@ public class CashTransferApiResource {
|
||||
private final GlDispatchService glDispatchService;
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final CashierRoleHelper cashierRoleHelper;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
@@ -200,9 +203,27 @@ public class CashTransferApiResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll() {
|
||||
List<CashTransfer> list = repository.findAllByOrderByCreatedAtDesc();
|
||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
||||
public String getAll(
|
||||
@QueryParam("offset") @jakarta.ws.rs.DefaultValue("0") int offset,
|
||||
@QueryParam("limit") @jakarta.ws.rs.DefaultValue("20") int limit) {
|
||||
final Long scopedCashierId = cashierRoleHelper.getCashierIdForFiltering();
|
||||
List<CashTransfer> all = repository.findAllByOrderByCreatedAtDesc();
|
||||
if (scopedCashierId != null) {
|
||||
all = all.stream()
|
||||
.filter(t -> scopedCashierId.equals(t.getToCashierId()))
|
||||
.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
|
||||
|
||||
@@ -342,4 +342,5 @@
|
||||
<include file="parts/0312_grant_cashier_fd_rd_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0313_alter_cash_exchange_add_columns.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"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?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 additional READ permissions to the CASHIER role required for
|
||||
the Cashier Transaction Entry screen to display full account details:
|
||||
- READ_CLIENTKYC → PAN, Aadhaar, CKYC number
|
||||
- READ_CLIENTNOMINEE → nominee list per account
|
||||
- READ_ADDRESS → client address
|
||||
- READ_FAMILYMEMBERS → S/D/W/H/O (guardian/spouse name)
|
||||
- READ_SAVINGSACCOUNT → savings account detail fetch
|
||||
-->
|
||||
<changeSet id="0315_grant_cashier_transaction_entry_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_CLIENTKYC',
|
||||
'READ_CLIENTNOMINEE',
|
||||
'READ_ADDRESS',
|
||||
'READ_FAMILYMEMBERS',
|
||||
'READ_SAVINGSACCOUNT'
|
||||
)
|
||||
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_CLIENTKYC',
|
||||
'READ_CLIENTNOMINEE',
|
||||
'READ_ADDRESS',
|
||||
'READ_FAMILYMEMBERS',
|
||||
'READ_SAVINGSACCOUNT'
|
||||
)
|
||||
);
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user