diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashreport/api/CashReportApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashreport/api/CashReportApiResource.java index e1b6e1fce..3e29e1d0c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashreport/api/CashReportApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashreport/api/CashReportApiResource.java @@ -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 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> rows = jdbcTemplate.query(sql, (rs, rowNum) -> { - Map 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> pageItems = jdbcTemplate.query(dataSql, (rs, rowNum) -> { + final Map 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 result = new LinkedHashMap<>(); + final Map 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);