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 lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
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.cashexchange.domain.CashExchange;
|
||||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||||
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeDetail;
|
import org.apache.fineract.portfolio.cashexchange.domain.CashExchangeDetail;
|
||||||
@@ -45,6 +46,7 @@ public class CashExchangeApiResource {
|
|||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
||||||
|
private final CashierRoleHelper cashierRoleHelper;
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -174,10 +176,30 @@ public class CashExchangeApiResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
public String getAll(@QueryParam("accountId") Long accountId) {
|
public String getAll(
|
||||||
List<CashExchange> list = accountId != null ? repository.findByAccountIdOrderByCreatedAtDesc(accountId)
|
@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();
|
: 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
|
@GET
|
||||||
|
|||||||
+25
-3
@@ -11,6 +11,7 @@ import jakarta.ws.rs.POST;
|
|||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.PathParam;
|
import jakarta.ws.rs.PathParam;
|
||||||
import jakarta.ws.rs.Produces;
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.QueryParam;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -22,6 +23,7 @@ import java.util.stream.Collectors;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
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.CashInterCashier;
|
||||||
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierDetail;
|
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierDetail;
|
||||||
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierDetailRepository;
|
import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierDetailRepository;
|
||||||
@@ -40,6 +42,7 @@ public class CashInterCashierApiResource {
|
|||||||
private final CashInterCashierRepository repository;
|
private final CashInterCashierRepository repository;
|
||||||
private final CashInterCashierDetailRepository detailRepository;
|
private final CashInterCashierDetailRepository detailRepository;
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
|
private final CashierRoleHelper cashierRoleHelper;
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -143,9 +146,28 @@ public class CashInterCashierApiResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
public String getAll() {
|
public String getAll(
|
||||||
return gson.toJson(repository.findAllByOrderByCreatedAtDesc().stream()
|
@QueryParam("offset") @jakarta.ws.rs.DefaultValue("0") int offset,
|
||||||
.map(this::toMap).collect(Collectors.toList()));
|
@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
|
@GET
|
||||||
|
|||||||
+30
-3
@@ -14,6 +14,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
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.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -27,6 +28,7 @@ public class CashReportApiResource {
|
|||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
private final DenominationBreakdownWriter denominationBreakdownWriter;
|
||||||
|
private final CashierRoleHelper cashierRoleHelper;
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -115,10 +117,15 @@ public class CashReportApiResource {
|
|||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("excess")
|
@Path("excess")
|
||||||
public String excess(@QueryParam("threshold") BigDecimal threshold, @QueryParam("fromDate") String fromDate,
|
public String excess(@QueryParam("threshold") BigDecimal threshold,
|
||||||
@QueryParam("toDate") String toDate) {
|
@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 BigDecimal effectiveThreshold = threshold != null ? threshold : BigDecimal.valueOf(50000);
|
||||||
final boolean hasDateRange = fromDate != null && !fromDate.isBlank() && toDate != null && !toDate.isBlank();
|
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
|
// Savings / Current / FD / RD transactions
|
||||||
final StringBuilder sql = new StringBuilder();
|
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 >= ? ");
|
exSql.append("WHERE (is_reversed IS NULL OR is_reversed = false) AND exchange_amount >= ? ");
|
||||||
final List<Object> exArgs = new ArrayList<>();
|
final List<Object> exArgs = new ArrayList<>();
|
||||||
exArgs.add(effectiveThreshold);
|
exArgs.add(effectiveThreshold);
|
||||||
|
if (scopedCashierId != null) {
|
||||||
|
exSql.append("AND cashier_id = ? ");
|
||||||
|
exArgs.add(scopedCashierId);
|
||||||
|
}
|
||||||
if (hasDateRange) {
|
if (hasDateRange) {
|
||||||
exSql.append("AND exchange_date BETWEEN CAST(? AS DATE) AND CAST(? AS DATE) ");
|
exSql.append("AND exchange_date BETWEEN CAST(? AS DATE) AND CAST(? AS DATE) ");
|
||||||
exArgs.add(fromDate);
|
exArgs.add(fromDate);
|
||||||
@@ -181,7 +192,17 @@ public class CashReportApiResource {
|
|||||||
combined.addAll(exchangeRows);
|
combined.addAll(exchangeRows);
|
||||||
combined.sort((a, b) -> ((BigDecimal) b.get("totalAmount")).compareTo((BigDecimal) a.get("totalAmount")));
|
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) {
|
@QueryParam("asOfDate") String asOfDate) {
|
||||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
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) {
|
if (cashierId != null) {
|
||||||
return gson.toJson(buildPositionForCashier(cashierId, dateFilter));
|
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 java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||||
|
import org.apache.fineract.portfolio.cashreport.service.CashierRoleHelper;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlement;
|
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlement;
|
||||||
@@ -49,6 +50,7 @@ public class CashSettlementApiResource {
|
|||||||
private final GlDispatchService glDispatchService;
|
private final GlDispatchService glDispatchService;
|
||||||
private final PlatformTransactionManager transactionManager;
|
private final PlatformTransactionManager transactionManager;
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
private final CashierRoleHelper cashierRoleHelper;
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -224,9 +226,27 @@ public class CashSettlementApiResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
public String getAll() {
|
public String getAll(
|
||||||
List<CashSettlement> list = repository.findAllByOrderByCreatedAtDesc();
|
@QueryParam("offset") @jakarta.ws.rs.DefaultValue("0") int offset,
|
||||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
@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
|
@GET
|
||||||
|
|||||||
+24
-3
@@ -11,6 +11,7 @@ import jakarta.ws.rs.POST;
|
|||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.PathParam;
|
import jakarta.ws.rs.PathParam;
|
||||||
import jakarta.ws.rs.Produces;
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.QueryParam;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -22,6 +23,7 @@ import java.util.Map;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
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.portfolio.teller.service.GlDispatchService;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.cashtransfer.domain.CashTransfer;
|
import org.apache.fineract.portfolio.cashtransfer.domain.CashTransfer;
|
||||||
@@ -48,6 +50,7 @@ public class CashTransferApiResource {
|
|||||||
private final GlDispatchService glDispatchService;
|
private final GlDispatchService glDispatchService;
|
||||||
private final PlatformTransactionManager transactionManager;
|
private final PlatformTransactionManager transactionManager;
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
private final CashierRoleHelper cashierRoleHelper;
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -200,9 +203,27 @@ public class CashTransferApiResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
public String getAll() {
|
public String getAll(
|
||||||
List<CashTransfer> list = repository.findAllByOrderByCreatedAtDesc();
|
@QueryParam("offset") @jakarta.ws.rs.DefaultValue("0") int offset,
|
||||||
return gson.toJson(list.stream().map(this::toMap).collect(Collectors.toList()));
|
@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
|
@GET
|
||||||
|
|||||||
@@ -342,4 +342,5 @@
|
|||||||
<include file="parts/0312_grant_cashier_fd_rd_permissions.xml" relativeToChangelogFile="true"/>
|
<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/0313_alter_cash_exchange_add_columns.xml" relativeToChangelogFile="true"/>
|
||||||
<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"/>
|
||||||
</databaseChangeLog>
|
</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