Compare commits
2
Commits
5a9a103832
...
c052beeacf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c052beeacf | ||
|
|
942bad6c8c |
+74
-1
@@ -494,6 +494,62 @@ public class BranchDayRegisterApiResource {
|
||||
"Business day " + businessDate + " is already closed.");
|
||||
}
|
||||
|
||||
// ── Ensure every cashier holding cash has been reconciled AND settled ──
|
||||
// Find all cashiers under tellers belonging to this office, check if they have a cash position,
|
||||
// and if so, verify reconciliation and settlement records exist for the business date.
|
||||
final List<Map<String, Object>> unsettledCashiers = jdbcTemplate.queryForList("""
|
||||
SELECT c.id AS cashier_id, c.staff_id,
|
||||
COALESCE(s.display_name, CONCAT('Cashier #', c.id)) AS cashier_name,
|
||||
(SELECT COUNT(*) FROM m_cash_reconciliation cr
|
||||
WHERE cr.cashier_id = c.id AND cr.recon_date = ?) AS recon_count,
|
||||
(SELECT COUNT(*) FROM m_cash_settlement cs
|
||||
WHERE cs.from_cashier_id = c.id AND cs.settlement_date = ?
|
||||
AND (cs.is_reversed IS NULL OR cs.is_reversed = false)) AS settle_count
|
||||
FROM m_cashiers c
|
||||
JOIN m_tellers t ON t.id = c.teller_id
|
||||
LEFT JOIN m_staff s ON s.id = c.staff_id
|
||||
WHERE t.office_id = ?
|
||||
AND (c.start_date IS NULL OR c.start_date <= ?)
|
||||
AND (c.end_date IS NULL OR c.end_date >= ?)
|
||||
AND (
|
||||
EXISTS (
|
||||
SELECT 1 FROM m_cash_transfer ct
|
||||
WHERE ct.to_cashier_id = c.id
|
||||
AND ct.transfer_date = ?
|
||||
AND (ct.is_reversed IS NULL OR ct.is_reversed = false)
|
||||
AND ct.transfer_amount > 0
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM m_cash_inter_cashier cic
|
||||
WHERE cic.to_cashier_id = c.id
|
||||
AND cic.transfer_date = ?
|
||||
AND (cic.is_reversed IS NULL OR cic.is_reversed = false)
|
||||
AND cic.transfer_amount > 0
|
||||
)
|
||||
)
|
||||
""", Date.valueOf(businessDate), Date.valueOf(businessDate),
|
||||
officeId, Date.valueOf(businessDate), Date.valueOf(businessDate),
|
||||
Date.valueOf(businessDate), Date.valueOf(businessDate));
|
||||
|
||||
final List<String> blockingCashiers = new ArrayList<>();
|
||||
for (final Map<String, Object> row : unsettledCashiers) {
|
||||
final int reconCount = ((Number) row.get("recon_count")).intValue();
|
||||
final int settleCount = ((Number) row.get("settle_count")).intValue();
|
||||
if (reconCount == 0 || settleCount == 0) {
|
||||
final String name = String.valueOf(row.get("cashier_name"));
|
||||
final List<String> issues = new ArrayList<>();
|
||||
if (reconCount == 0) issues.add("Not Reconciled");
|
||||
if (settleCount == 0) issues.add("Not Settled");
|
||||
blockingCashiers.add(name + " (" + String.join(", ", issues) + ")");
|
||||
}
|
||||
}
|
||||
if (!blockingCashiers.isEmpty()) {
|
||||
throw new PlatformDataIntegrityException("error.msg.branchday.cashiers.not.settled",
|
||||
"Cannot close the day — the following cashiers have not been reconciled or settled: "
|
||||
+ String.join("; ", blockingCashiers)
|
||||
+ ". Each cashier must reconcile their cash and settle it back to the vault before the branch day can be closed.");
|
||||
}
|
||||
|
||||
// Closing a past date is allowed — otherwise a day left open could never be settled at all —
|
||||
// but it must say why it was left open, and it is stamped as a force close.
|
||||
final LocalDate today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||
@@ -853,12 +909,29 @@ public class BranchDayRegisterApiResource {
|
||||
// disbursements use APPROVED; withdrawal is the one register that doesn't) -- filtering on
|
||||
// APPROVED alone silently excluded every completed withdrawal from this total, overstating
|
||||
// the branch's expected cash by exactly what had actually been paid out that day.
|
||||
final BigDecimal withdrawals = zero(jdbcTemplate.queryForObject("""
|
||||
final BigDecimal slipWithdrawals = zero(jdbcTemplate.queryForObject("""
|
||||
SELECT COALESCE(SUM(amount), 0) FROM m_clerk_withdrawal_request
|
||||
WHERE status = 'WITHDRAWN'
|
||||
AND COALESCE(txn_date, CAST(submitted_at AS DATE)) = ?
|
||||
""", BigDecimal.class, date));
|
||||
|
||||
// Account closures pay cash over the same counter but live in their own register, so they
|
||||
// were missing from this total entirely -- the branch's expected cash stayed high by exactly
|
||||
// what had been handed over, reporting a shortage for a closure that balanced perfectly.
|
||||
//
|
||||
// cashier_disbursed_amount, not closing_amount: the member's ledger gives up the paisa-exact
|
||||
// figure, but what physically leaves the drawer is that figure rounded to whole rupees, and
|
||||
// this total has to agree with a physical count. Only CLOSED rows, dated on disbursed_at,
|
||||
// since a closure takes nothing out of the drawer until the cashier hands it over (a
|
||||
// nil-balance closure never reaches a cashier at all, hence the closure_date fallback).
|
||||
final BigDecimal closureWithdrawals = zero(jdbcTemplate.queryForObject("""
|
||||
SELECT COALESCE(SUM(cashier_disbursed_amount), 0) FROM m_clerk_closure_request
|
||||
WHERE status = 'CLOSED'
|
||||
AND COALESCE(CAST(disbursed_at AS DATE), closure_date) = ?
|
||||
""", BigDecimal.class, date));
|
||||
|
||||
final BigDecimal withdrawals = slipWithdrawals.add(closureWithdrawals);
|
||||
|
||||
// Cash drawn from the vault to a cashier — more physical cash sitting in the branch.
|
||||
final BigDecimal vaultIn = zero(jdbcTemplate.queryForObject("""
|
||||
SELECT COALESCE(SUM(transfer_amount), 0) FROM m_cash_transfer
|
||||
|
||||
Reference in New Issue
Block a user