feat(cashier): add server-side pagination and date ordering to GL entries endpoint

- Add offset/limit query params with defaults (limit=20, offset=0)
- Return totalFilteredRecords + pageItems response structure
- Compute totalDr/totalCr from all records (not just current page)
- Fix ordering: ORDER BY MAX(created_at) for correct chronological sort

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-10 15:53:49 +05:30
co-authored by Claude Sonnet 4.6
parent 19639c9e02
commit db94056144
@@ -558,34 +558,59 @@ public class CashReportApiResource {
@Path("gl-entries")
public String glEntries(@QueryParam("date") String date,
@QueryParam("cashierId") Long cashierId,
@QueryParam("tellerId") Long tellerId) {
@QueryParam("tellerId") Long tellerId,
@QueryParam("offset") Integer offset,
@QueryParam("limit") Integer limit) {
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
final int resolvedLimit = (limit != null && limit > 0) ? limit : 20;
final int resolvedOffset = (offset != null && offset >= 0) ? offset : 0;
// Build cashier officer_id filter
String officerFilter;
Object[] params;
Object[] baseParams;
if (cashierId != null) {
officerFilter = "AND std.officer_id IN ("
+ "SELECT au.id FROM m_appuser au "
+ "JOIN m_cashiers mc ON mc.staff_id = au.staff_id WHERE mc.id = ?)";
params = new Object[] { txnDate, cashierId };
baseParams = new Object[] { txnDate, cashierId };
} else if (tellerId != null) {
officerFilter = "AND std.officer_id IN ("
+ "SELECT au.id FROM m_appuser au "
+ "JOIN m_cashiers mc ON mc.staff_id = au.staff_id "
+ "JOIN m_tellers mt ON mt.id = mc.teller_id WHERE mt.id = ?)";
params = new Object[] { txnDate, tellerId };
baseParams = new Object[] { txnDate, tellerId };
} else {
officerFilter = "AND std.officer_id IN ("
+ "SELECT au.id FROM m_appuser au "
+ "JOIN m_cashiers mc ON mc.staff_id = au.staff_id)";
params = new Object[] { txnDate };
baseParams = new Object[] { txnDate };
}
// One row per transaction — account no, account holder name, dr/cr amount, date, teller, payment type
final String sql = "SELECT "
// Base grouped query (reused for count, totals, and paged data)
final String baseGroupSql = "FROM m_savings_txn_denomination std "
+ "LEFT JOIN m_client mc ON mc.id = std.client_id "
+ "WHERE std.txn_date = CAST(? AS DATE) "
+ officerFilter
+ " GROUP BY std.txn_id, std.account_no, mc.display_name, std.cif_no ";
// 1. Count + totals for all matching records (no pagination)
final String totalsSql = "SELECT COUNT(*) AS total_count, "
+ " COALESCE(SUM(dr_amount), 0) AS total_dr, "
+ " COALESCE(SUM(cr_amount), 0) AS total_cr "
+ "FROM (SELECT "
+ " SUM(CASE WHEN std.txn_type = 'WITHDRAWAL' THEN std.total_amount ELSE 0 END) AS dr_amount, "
+ " SUM(CASE WHEN std.txn_type = 'DEPOSIT' THEN std.total_amount ELSE 0 END) AS cr_amount "
+ baseGroupSql + ") sub";
final Map<String, Object> totalsRow = jdbcTemplate.queryForMap(totalsSql, baseParams);
final long totalFilteredRecords = ((Number) totalsRow.get("total_count")).longValue();
final java.math.BigDecimal totalDr = (java.math.BigDecimal) totalsRow.get("total_dr");
final java.math.BigDecimal totalCr = (java.math.BigDecimal) totalsRow.get("total_cr");
// 2. Paged data query
final String dataSql = "SELECT "
+ " std.txn_id AS voucher_no, "
+ " std.account_no AS ac_no, "
+ " COALESCE(mc.display_name, std.cif_no, '') AS ac_name, "
@@ -596,15 +621,18 @@ public class CashReportApiResource {
+ " CASE WHEN MAX(std.client_id) IS NULL THEN 'GL' ELSE 'SAVINGS' END AS txn_source, "
+ " SUM(CASE WHEN std.txn_type = 'WITHDRAWAL' THEN std.total_amount ELSE 0 END) AS dr_amount, "
+ " SUM(CASE WHEN std.txn_type = 'DEPOSIT' THEN std.total_amount ELSE 0 END) AS cr_amount "
+ "FROM m_savings_txn_denomination std "
+ "LEFT JOIN m_client mc ON mc.id = std.client_id "
+ "WHERE std.txn_date = CAST(? AS DATE) "
+ officerFilter
+ " GROUP BY std.txn_id, std.account_no, mc.display_name, std.cif_no "
+ "ORDER BY std.txn_id NULLS LAST, MAX(std.created_at)";
+ baseGroupSql
+ "ORDER BY MAX(std.created_at) "
+ "LIMIT ? OFFSET ?";
List<Map<String, Object>> rows = jdbcTemplate.query(sql, (rs, rowNum) -> {
Map<String, Object> row = new LinkedHashMap<>();
// Append limit and offset to params
final Object[] pagedParams = new Object[baseParams.length + 2];
System.arraycopy(baseParams, 0, pagedParams, 0, baseParams.length);
pagedParams[baseParams.length] = resolvedLimit;
pagedParams[baseParams.length + 1] = resolvedOffset;
final List<Map<String, Object>> pageItems = jdbcTemplate.query(dataSql, (rs, rowNum) -> {
final Map<String, Object> row = new LinkedHashMap<>();
row.put("voucherNo", rs.getLong("voucher_no"));
row.put("acNo", rs.getString("ac_no"));
row.put("acName", rs.getString("ac_name"));
@@ -616,18 +644,12 @@ public class CashReportApiResource {
row.put("dr", rs.getBigDecimal("dr_amount"));
row.put("cr", rs.getBigDecimal("cr_amount"));
return row;
}, params);
}, pagedParams);
java.math.BigDecimal totalDr = rows.stream()
.map(r -> (java.math.BigDecimal) r.get("dr"))
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
java.math.BigDecimal totalCr = rows.stream()
.map(r -> (java.math.BigDecimal) r.get("cr"))
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
Map<String, Object> result = new LinkedHashMap<>();
final Map<String, Object> result = new LinkedHashMap<>();
result.put("date", txnDate);
result.put("rows", rows);
result.put("totalFilteredRecords", totalFilteredRecords);
result.put("pageItems", pageItems);
result.put("totalDr", totalDr);
result.put("totalCr", totalCr);
return gson.toJson(result);