@extends('layouts.app') @section('title', 'Cash Recon') @section('content') @php $isAdmin = auth()->user()->hasAnyRole('Super Admin', 'Hospital Admin'); @endphp

Cash Recon

Daily cash reconciliation by shift · {{ now()->format('l, d M Y · H:i') }}

{{-- Date + Shift selector form. GET so URL stays bookmarkable. Defaults to today + current shift; receptionist can change to any past date for backfills. (Issue #17 — May 2 2026) --}}
@if(request()->hasAny(['date', 'shift'])) Back to Today @endif
@if(session('success'))
{{ session('success') }} @if($nextMissing) Next Missing: {{ $nextMissing->label }} → @endif
@endif {{-- Shift window banner --}}
{{ ucfirst($shiftLabel) }} Shift · Anchor date: {{ \Carbon\Carbon::parse($shiftDate)->format('d M Y') }} · Window: {{ $startsAt->format('d M, H:i') }} → {{ $endsAt->format('d M, H:i') }}
@if($existingClosing)

⚠ A closing has already been submitted for this shift.

Submitted by {{ $existingClosing->cashier->name ?? '—' }} on {{ $existingClosing->created_at->format('d M, H:i') }}. Cash collected: KES {{ number_format($existingClosing->cash_collected, 2) }}; deposited: KES {{ number_format($existingClosing->amount_deposited, 2) }}; variance: {{ $existingClosing->variance >= 0 ? '+' : '' }}{{ number_format($existingClosing->variance, 2) }}.

Submitting again will create a second row (rare scenarios like split shifts may need this).

@endif {{-- Recon panel --}}

Reconciliation

Cash collected and expenses are computed live from the system.

{{-- Cash expenses row: clickable to expand the breakdown. Expense payments are usually 0–3 per shift but receptionists have asked to see them line-by-line so they can confirm against physical receipts before submitting the deposit. --}} {{-- Expandable detail row. Hidden by default so the panel stays compact for the 90 % case where the deduction is small or zero. --}}
Cash collected (system) {{ number_format($cashCollected, 2) }}
− Cash expenses paid ({{ $cashExpensePayments->count() }} item{{ $cashExpensePayments->count() === 1 ? '' : 's' }}) {{ number_format($cashExpenses, 2) }}
Expected to deposit {{ number_format($expectedToDeposit, 2) }}
{{-- Outstanding shifts block (Q2-C policy) If the cashier has earlier shifts that were submitted without a deposit, they can't record a new one until those are cleared. Admin roles bypass this check on the controller side, so for them this panel still renders but the button stays enabled. --}} @if(isset($outstandingShifts) && $outstandingShifts->isNotEmpty()) @php $isAdminBypass = Auth::user()->hasAnyRole('Super Admin', 'Hospital Admin', 'Finance User'); @endphp

{{ $outstandingShifts->count() }} undeposited shift{{ $outstandingShifts->count() === 1 ? '' : 's' }} must be cleared first

@if($isAdminBypass) (You are an admin — you can still submit, but the cashier responsible needs to deposit these.) @else Record the deposit for these earlier shifts before submitting a new one. Click any row to jump there. @endif

@endif {{-- Submit form --}}

Record Deposit

@if($errors->any())
    @foreach($errors->all() as $e)
  • {{ $e }}
  • @endforeach
@endif
@csrf {{-- Time-window banner. Always shown — green if open, amber if not-yet-open, red if closed. Admins see "admin override active" so they know normal cashiers wouldn't see this as submittable right now. --}} @php $isAdmin = auth()->user()->hasAnyRole('Super Admin','Hospital Admin'); $windowOpen = $submitWindow['allowed'] ?? true; $opensAt = $submitWindow['opens_at'] ?? null; $closesAt = $submitWindow['closes_at'] ?? null; $windowReason = $submitWindow['reason'] ?? ''; // For admins, $windowOpen is always true. We still want to // visually convey whether a normal cashier could submit // right now, so recompute the unprivileged version. $now = now(); $cashierWouldBeBlocked = $opensAt && $closesAt && ($now->lt($opensAt) || $now->gt($closesAt)); @endphp @if($opensAt && $closesAt) @if($isAdmin && $cashierWouldBeBlocked)
Admin override active. Outside cashier window (open {{ $opensAt->format('H:i, d M') }} – {{ $closesAt->format('H:i, d M') }}). A cashier wouldn't be able to submit this right now.
@elseif(!$windowOpen)
Cash recon locked. {{ $windowReason }}
@else
Recon window open until {{ $closesAt->format('H:i, d M') }}.
@endif @endif

Pre-filled with expected; edit to actual deposited amount.

Positive = shortage. Negative = over-deposit.

@php $hasOutstanding = isset($outstandingShifts) && $outstandingShifts->isNotEmpty() && !Auth::user()->hasAnyRole('Super Admin', 'Hospital Admin', 'Finance User'); // Submit is blocked if EITHER outstanding shifts exist OR the // time window is closed for this user. Admin bypasses both. $blockSubmit = $hasOutstanding || !$windowOpen; $blockReason = !$windowOpen ? $windowReason : ($hasOutstanding ? 'Clear the outstanding shifts above first' : ''); @endphp
{{-- Missing Closings panel — shifts with cash activity but no recon row. Always rendered if any are detected. (Issue #17 May 2 2026) --}} @if($missingShifts->isNotEmpty())

Missing Closings

{{ $missingShifts->count() }} shift(s) had cash activity but no recon was submitted. Click any row to load its window.

Click a row → form fills in for that shift.
@foreach($missingShifts as $m) @endforeach
Date Shift Expected to Deposit
{{ $m->date->format('d M Y') }} {{ $m->date->format('D') }} {{ $m->shift === 'day' ? '☀ Day' : '☾ Night' }} KES {{ number_format($m->expected, 2) }} Load →
@else
✓ All shifts with cash activity have been recon'd. No backfills needed.
@endif {{-- Recent closings --}}

Recent Closings

@if($recentClosings->isEmpty())
No closings recorded yet.
@else @php // Admin tick: only Hospital Admin and Super Admin can verify // a cashier's submitted deposit. We compute once and reuse so // the per-row check is cheap. $canVerifyDeposits = auth()->user()->hasAnyRole('Super Admin', 'Hospital Admin'); @endphp @foreach($recentClosings as $c) @php $expected = (float) $c->cash_collected - (float) $c->cash_expenses; @endphp @endforeach
Date Shift Cashier Collected Expenses Expected Deposited Variance M-Pesa Ref Verified
{{ $c->date->format('d M') }} {{ $c->shift }} {{ $c->cashier->name ?? '—' }} {{ number_format($c->cash_collected, 2) }} {{ number_format($c->cash_expenses, 2) }} {{ number_format($expected, 2) }} {{ number_format($c->amount_deposited, 2) }} {{ $c->variance >= 0 ? '+' : '' }}{{ number_format($c->variance, 2) }} {{ $c->mpesa_deposit_code ?: $c->deposit_reference ?: '—' }} @if($c->verified_at) @if($canVerifyDeposits) {{-- Admin view: green "Verified" badge with hover tooltip showing who verified and when. --}} Verified @else {{-- Receptionist view post-verification: small grey confirmation so they know it was reviewed, but nothing they can act on. Tooltip carries the who/when so they can answer questions if asked. --}} Verified @endif @else @if($canVerifyDeposits) {{-- Admin view: yellow clickable tick button. Confirms on submit so admin can't fat-finger an entire shift's deposits by accident. --}}
@csrf
@else {{-- Receptionist view pre-verification: yellow non-clickable pill so they can see it's pending but not act on it. Disappears into a small grey badge once admin verifies (see the branch above). --}} Pending @endif @endif
@endif
@push('scripts') @endpush @endsection