@extends('layouts.app') @section('title', 'Expenses') @section('styles') @endsection @section('content') @php $isSuperAdmin = auth()->user()->hasRole('Super Admin'); @endphp

Expenses

Record obligations, track payments, monitor overdue items

@if($suppliers->isEmpty()) No suppliers yet — add one → @endif
@if(session('success'))
{{ session('success') }}
@endif @if(session('warning'))
{{ session('warning') }}
@endif {{-- Stats cards removed per round 5 iteration. The numbers were duplicated elsewhere (Reports + Dashboard) and added noise to the Expenses page. --}}
{{-- TAB: All Expenses --}}
@if(request()->hasAny(['status','category','supplier_id','payment_method','from','to','show_all']))Clear@endif
{{-- Department + Due Date columns removed per May 2 simplification. Category column removed per round 5 (filter dropdown above still works). Edit column shown only for Super Admin. --}} @php // Span the date cell only over CONTIGUOUS same-date rows. // The list isn't always ordered strictly by date (e.g. a paid // item can sit between two unpaid ones), so counting every // same-date row globally made the rowspan bleed into the next // day and shove that row's cells one column to the right — // suppliers ended up under the Date column. $expIterable = method_exists($expenses, 'getCollection') ? $expenses->getCollection() : $expenses; $expRows = collect($expIterable)->values(); $expRunSize = []; $expCnt = $expRows->count(); for ($i = 0; $i < $expCnt; ) { $d = optional($expRows[$i]->date)->format('Y-m-d'); $j = $i + 1; while ($j < $expCnt && optional($expRows[$j]->date)->format('Y-m-d') === $d) { $j++; } $expRunSize[$i] = $j - $i; $i = $j; } $prevExpDate = null; @endphp @forelse($expenses as $ex) @php $esc=['pending'=>'badge-warning','partially_paid'=>'badge-info','paid'=>'badge-success','overdue'=>'badge-danger','cancelled'=>'badge-gray']; // Resolve payment method for display: // - no payments yet → '—' // - one method across payments → that method // - more than one → 'Mixed' (rare; partial-pay scenarios) $methods = $ex->payments->pluck('payment_method')->filter()->unique(); if ($methods->count() === 0) { $methodLabel = '—'; $methodClass = 'text-gray-300'; } elseif ($methods->count() === 1) { $methodLabel = ['cash'=>'Cash','mpesa'=>'M-Pesa','bank_transfer'=>'Bank','cheque'=>'Cheque'][$methods->first()] ?? ucfirst($methods->first()); // Highlight cash so it's visually scannable on the page // — cash recon literally only cares about this one. $methodClass = $methods->first() === 'cash' ? 'text-orange-600 font-semibold' : 'text-gray-600'; } else { $methodLabel = 'Mixed'; $methodClass = 'text-purple-600 font-semibold'; } // Latest payment for the edit modal — Super Admin only. $latestPayment = $ex->payments->sortByDesc('id')->first(); $editPaymentPayload = $latestPayment ? [ 'expense_id' => $ex->id, 'description' => $ex->description, 'supplier' => $ex->supplier->name ?? $ex->vendor ?? null, 'amount' => $latestPayment->amount, 'payment_method' => $latestPayment->payment_method, 'reference_number' => $latestPayment->reference_number, 'payment_date' => optional($latestPayment->payment_date)->format('d M Y'), ] : []; // Date-merge logic: only render the Date @if($isFirstOfDay) @endif {{-- Category column removed (round 5). Filter dropdown above still works. --}} {{-- Prefer linked supplier name; fall back to legacy 'vendor' string for old rows that pre-date the suppliers tab. --}} @empty @endforelse
Date Supplier / Payee Description Amount Paid Method Balance Status Actions
on the first // row of each day, with rowspan = count of that day's rows. $thisExpDate = $ex->date->format('Y-m-d'); $isFirstOfDay = $thisExpDate !== $prevExpDate; $expGroupSize = $expRunSize[$loop->index] ?? 1; $prevExpDate = $thisExpDate; @endphp
{{ $ex->date->format('d M Y') }}{{ $ex->supplier->name ?? $ex->vendor ?? '-' }} {{ $ex->description }} {{ number_format($ex->amount) }} {{ number_format($ex->amount_paid ?? 0) }} {{ $methodLabel }} {{ number_format($ex->balance ?? $ex->amount) }} {{ ucfirst(str_replace('_',' ',$ex->status ?? 'pending')) }}
@if(($ex->status ?? 'pending') !== 'paid' && ($ex->status ?? 'pending') !== 'cancelled') @endif @if($isSuperAdmin && $latestPayment) {{-- Super Admin only: edit the most recent payment's method/reference. Backfill mechanism for expenses logged before payment_method was a required field. --}} @endif
No expenses recorded. Click + Add Expense to create one.
@if($expenses->hasPages())
{{ $expenses->links() }}
@endif
{{-- TAB: Unpaid / Overdue --}}
{{-- Due Date column kept here because overdue tracking is the whole point of this tab; only the All Expenses table dropped it. --}} @php // Date-merge helper for the unpaid table (contiguous runs). $unpaidRows = ($unpaidExpenses ?? collect())->values(); $unpaidRunSize = []; $uCnt = $unpaidRows->count(); for ($i = 0; $i < $uCnt; ) { $d = optional($unpaidRows[$i]->date)->format('Y-m-d'); $j = $i + 1; while ($j < $uCnt && optional($unpaidRows[$j]->date)->format('Y-m-d') === $d) { $j++; } $unpaidRunSize[$i] = $j - $i; $i = $j; } $prevUnpaidDate = null; // Running total of balances for the tfoot row. $totalUnpaid = ($unpaidExpenses ?? collect())->sum(fn($e) => $e->balance ?? $e->amount); @endphp @forelse($unpaidExpenses ?? [] as $ex) @php $thisUnpaidDate = $ex->date->format('Y-m-d'); $isFirstOfDayU = $thisUnpaidDate !== $prevUnpaidDate; $unpaidGroupSize = $unpaidRunSize[$loop->index] ?? 1; $prevUnpaidDate = $thisUnpaidDate; @endphp @if($isFirstOfDayU) @endif @empty @endforelse {{-- Total Unpaid footer — sum of all balances across visible rows. --}} @if(($unpaidExpenses ?? collect())->count() > 0) @endif
Date Category Supplier Description Amount Balance Due Date Status Action
{{ $ex->date->format('d M Y') }}{{ $ex->category }} {{ $ex->supplier->name ?? $ex->vendor ?? '-' }} {{ $ex->description }} {{ number_format($ex->amount) }} {{ number_format($ex->balance ?? $ex->amount) }} {{ $ex->due_date?->format('d M Y') ?? '-' }} {{ ($ex->is_overdue ?? false) ? 'Overdue' : ucfirst(str_replace('_',' ',$ex->status ?? 'pending')) }}
No unpaid or overdue expenses.
Total Unpaid KES {{ number_format($totalUnpaid) }}
{{-- TAB: Payment History --}}
@php // Date-merge helper for the payment history table — // grouped by payment_date (contiguous runs). $payRows = ($expensePayments ?? collect())->values(); $payRunSize = []; $pCnt = $payRows->count(); for ($i = 0; $i < $pCnt; ) { $d = optional($payRows[$i]->payment_date)->format('Y-m-d'); $j = $i + 1; while ($j < $pCnt && optional($payRows[$j]->payment_date)->format('Y-m-d') === $d) { $j++; } $payRunSize[$i] = $j - $i; $i = $j; } $prevPayDate = null; @endphp @forelse($expensePayments ?? [] as $ep) @php $thisPayDate = $ep->payment_date?->format('Y-m-d'); $isFirstOfDayP = $thisPayDate !== $prevPayDate; $payGroupSize = $payRunSize[$loop->index] ?? 1; $prevPayDate = $thisPayDate; @endphp @if($isFirstOfDayP) @endif @empty @endforelse
DateExpenseSupplierAmount PaidMethodReferencePaid ByShift
{{ $ep->payment_date->format('d M Y') }}{{ $ep->expense->description ?? '' }} {{ $ep->expense->supplier->name ?? $ep->expense->vendor ?? '-' }} KES {{ number_format($ep->amount) }} {{ str_replace('_',' ',$ep->payment_method) }} {{ $ep->reference_number ?? '-' }} {{ $ep->payer->name ?? '-' }} {{ $ep->shift ?? '-' }}
No expense payments recorded yet.
{{-- TAB: Suppliers (May 12 2026 — folded in from the standalone Suppliers sidebar entry so receptionists & finance can manage the payee list without leaving the Expenses module). --}} @php $supAdmin = auth()->user()->hasAnyRole('Super Admin', 'Hospital Admin'); @endphp
{{-- Preserve any expense-tab filters so switching tabs doesn't blow them away. --}} @foreach(['status','category','supplier_id','payment_method','from','to'] as $k) @if(request($k))@endif @endforeach
@foreach([ ['active','Active', $supplierCounts['active']], ['inactive','Inactive', $supplierCounts['inactive']], ['all','All', $supplierCounts['active']+$supplierCounts['inactive']], ] as [$val,$label,$count]) @endforeach
@if(request()->hasAny(['sup_q','sup_show']))Clear@endif
@if($supAdmin) @endif
@if($supAdmin)@endif @forelse($suppliersList as $s) @if($supAdmin) @endif @empty @endforelse
Name Contact Person Phone KRA PIN Category StatusActions
{{ $s->name }} {{ $s->contact_person ?? '-' }} {{ $s->phone ?? '-' }} {{ $s->kra_pin ?? '-' }} {{ $s->category ?? '-' }} @if($s->is_active) Active @else Inactive @endif
@csrf
No suppliers found. @if($supAdmin && !request('sup_q'))Click + Add Supplier to create one.@endif
@if($suppliersList->hasPages())
{{ $suppliersList->links() }}
@endif
{{-- ═══════════════════════════════════════════════════════════════════ --}} {{-- Add Expense Modal --}} {{-- ═══════════════════════════════════════════════════════════════════ --}} {{-- ═══════════════════════════════════════════════════════════════════ --}} {{-- Record Expense Payment Modal (the "Pay" button on unpaid rows) --}} {{-- ═══════════════════════════════════════════════════════════════════ --}} @if($isSuperAdmin) {{-- ═══════════════════════════════════════════════════════════════════ --}} {{-- Edit Posted Expense Payment Modal — Super Admin only --}} {{-- Used to backfill correct payment method on historical expenses so --}} {{-- cash recon reconciles backwards. --}} {{-- ═══════════════════════════════════════════════════════════════════ --}} @endif {{-- ═══════════════════════════════════════════════════════════════════ --}} {{-- Add / Edit Supplier Modal (Suppliers tab) --}} {{-- ═══════════════════════════════════════════════════════════════════ --}} @if($supAdmin) @endif @endsection @section('scripts') @endsection