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 f9c5c3031..ebb53d31e 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 @@ -172,6 +172,60 @@ public class CashReportApiResource { return gson.toJson(buildPositionForCashier(cashierId, dateFilter)); } + /** + * GET /cashreports/cashier-morning?cashierId=X&date=YYYY-MM-DD + * Returns denomination-wise cash received by the cashier from vault on the given date + * (today's morning opening allocation from m_cash_transfer only, non-reversed). + */ + @GET + @Path("cashier-morning") + public String cashierMorning(@QueryParam("cashierId") Long cashierId, + @QueryParam("date") String date) { + final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString(); + if (cashierId == null) { + Map empty = new LinkedHashMap<>(); + empty.put("cashierId", null); + empty.put("date", txnDate); + empty.put("totalMorning", BigDecimal.ZERO); + empty.put("denominations", new ArrayList<>()); + return gson.toJson(empty); + } + + final String sql = "SELECT ctd.denomination_id, ctd.denomination_value, " + + "COALESCE(dm.kind, 'note') AS kind, " + + "SUM(ctd.count) AS total_count, " + + "SUM(ctd.total_amount) AS total_amount " + + "FROM m_cash_transfer_detail ctd " + + "JOIN m_cash_transfer ct ON ct.id = ctd.transfer_id " + + "LEFT JOIN m_denomination_master dm ON dm.id = ctd.denomination_id " + + "WHERE ct.to_cashier_id = ? " + + "AND ct.transfer_date = CAST(? AS DATE) " + + "AND (ct.is_reversed IS NULL OR ct.is_reversed = false) " + + "GROUP BY ctd.denomination_id, ctd.denomination_value, dm.kind " + + "ORDER BY ctd.denomination_value DESC"; + + List> rows = jdbcTemplate.query(sql, (rs, rowNum) -> { + Map row = new LinkedHashMap<>(); + row.put("denominationId", rs.getLong("denomination_id")); + row.put("denominationValue", rs.getInt("denomination_value")); + row.put("kind", rs.getString("kind")); + row.put("morningCount", rs.getLong("total_count")); + row.put("morningAmount", rs.getBigDecimal("total_amount")); + return row; + }, cashierId, txnDate); + + BigDecimal total = rows.stream() + .map(r -> (BigDecimal) r.get("morningAmount")) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + Map result = new LinkedHashMap<>(); + result.put("cashierId", cashierId); + result.put("date", txnDate); + result.put("totalMorning", total); + result.put("denominations", rows); + return gson.toJson(result); + } + private Map buildPositionForCashier(Long cashierId, String dateFilter) { // Vault → Cashier allocations — GROUP BY denomination_id+kind to keep coin and note separate final String sqlIn1 = "SELECT ctd.denomination_id, ctd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(ctd.count) AS total_count " diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashsettlement/api/CashSettlementApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashsettlement/api/CashSettlementApiResource.java index 92d8975a6..514bed804 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashsettlement/api/CashSettlementApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashsettlement/api/CashSettlementApiResource.java @@ -132,7 +132,16 @@ public class CashSettlementApiResource { String glWarning = null; try { - glDispatchService.settleCashFromCashier(toTellerId, fromCashierId, total, + // Use cashier's own teller_id from m_cashiers for GL dispatch so the + // Fineract cashier-teller link is respected regardless of which vault teller + // the user submitted cash to. + Long glTellerId = toTellerId; + try { + Long resolved = jdbcTemplate.queryForObject( + "SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, fromCashierId); + if (resolved != null) glTellerId = resolved; + } catch (Exception ignored) {} + glDispatchService.settleCashFromCashier(glTellerId, fromCashierId, total, currencyCode, settlementDate.format(DateTimeFormatter.ISO_LOCAL_DATE), note != null ? note : ""); } catch (Exception e) { @@ -176,7 +185,13 @@ public class CashSettlementApiResource { if (settlement.getToTellerId() != null && settlement.getFromCashierId() != null) { try { - glDispatchService.allocateCashToCashier(settlement.getToTellerId(), + Long glTellerId = settlement.getToTellerId(); + try { + Long resolved = jdbcTemplate.queryForObject( + "SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, settlement.getFromCashierId()); + if (resolved != null) glTellerId = resolved; + } catch (Exception ignored) {} + glDispatchService.allocateCashToCashier(glTellerId, settlement.getFromCashierId(), settlement.getSettlementAmount(), "INR", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE), "Reversal of cash settlement #" + id + (reason != null ? ": " + reason : "")); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashtransfer/api/CashTransferApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashtransfer/api/CashTransferApiResource.java index f4137d60b..00085fd92 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashtransfer/api/CashTransferApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashtransfer/api/CashTransferApiResource.java @@ -29,6 +29,7 @@ import org.apache.fineract.portfolio.cashtransfer.domain.CashTransferDetail; import org.apache.fineract.portfolio.cashtransfer.domain.CashTransferDetailRepository; import org.apache.fineract.portfolio.cashtransfer.domain.CashTransferRepository; import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Transactional; @@ -46,6 +47,7 @@ public class CashTransferApiResource { private final PlatformSecurityContext context; private final GlDispatchService glDispatchService; private final PlatformTransactionManager transactionManager; + private final JdbcTemplate jdbcTemplate; private final Gson gson = new Gson(); @POST @@ -122,8 +124,18 @@ public class CashTransferApiResource { String glWarning = null; try { + // Use the cashier's own teller_id from m_cashiers for GL dispatch. + // Fineract's allocateCashToCashier requires cashier and teller to be linked — + // the user-selected fromTellerId is the business vault but may differ from the + // cashier's registered teller_id. Always resolve from DB to avoid GL failures. + Long glTellerId = fromTellerId; + try { + Long resolved = jdbcTemplate.queryForObject( + "SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, toCashierId); + if (resolved != null) glTellerId = resolved; + } catch (Exception ignored) {} final LocalDate txnDate = transferDate != null ? transferDate : LocalDate.now(); - glDispatchService.allocateCashToCashier(fromTellerId, toCashierId, total, + glDispatchService.allocateCashToCashier(glTellerId, toCashierId, total, currencyCode, txnDate.format(DateTimeFormatter.ISO_LOCAL_DATE), note != null ? note : ""); } catch (Exception e) { @@ -168,7 +180,13 @@ public class CashTransferApiResource { if (transfer.getFromTellerId() != null && transfer.getToCashierId() != null) { try { - glDispatchService.settleCashFromCashier(transfer.getFromTellerId(), + Long glTellerId = transfer.getFromTellerId(); + try { + Long resolved = jdbcTemplate.queryForObject( + "SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, transfer.getToCashierId()); + if (resolved != null) glTellerId = resolved; + } catch (Exception ignored) {} + glDispatchService.settleCashFromCashier(glTellerId, transfer.getToCashierId(), transfer.getTransferAmount(), "INR", java.time.LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE), "Reversal of cash transfer #" + id + (reason != null ? ": " + reason : "")); @@ -261,6 +279,10 @@ public class CashTransferApiResource { map.put("performedBy", e.getPerformedBy()); map.put("performedByUsername", e.getPerformedByUsername()); map.put("createdAt", e.getCreatedAt() != null ? e.getCreatedAt().toString() : null); + map.put("isReversed", Boolean.TRUE.equals(e.getIsReversed())); + map.put("reversedBy", e.getReversedBy()); + map.put("reversedAt", e.getReversedAt() != null ? e.getReversedAt().toString() : null); + map.put("reverseReason", e.getReverseReason()); return map; }