@extends('layouts.app') @section('title', 'Dispensing Tab') @section('styles') @endsection @section('content') {{-- Page header and global search removed per UI cleanup; each tab has its own filtering where appropriate. --}} {{-- Stats removed Issue #15: tab counts in the next block convey the same information (Queue / Dispensed Today / Partial) without the visual weight. Low Stock is still surfaced via the Low Stock submenu / dedicated drugs page. --}} {{-- Tabs --}}
{{-- TAB 1: PHARMACY QUEUE --}}
Filter: All OPD IPD Paid Unpaid Urgent
{{-- 25 May 2026 column changes: • Visit / Adm No. column removed • Drugs column removed • Rows now group at (patient, visit) so one patient on one visit is one row regardless of how many prescription pads were issued. The drug count (with a "across N pads" note when a doctor wrote multiple pads) now sits inline in the Status column so the pharmacist can still see how much work each row represents. --}} @forelse($pendingRx ?? [] as $row) @php // Composite key: matches how queueStockByGroup is // built in the controller. visit_id=0 is the // legacy "no-visit" bucket. $groupKey = $row->patient_id . '-' . ((int) ($row->visit_id ?? 0)); $patient = $queuePatients[$row->patient_id] ?? null; $visit = ((int) $row->visit_id > 0) ? ($queueVisits[$row->visit_id] ?? null) : null; $user = $row->prescribed_by ? ($queuePrescribers[$row->prescribed_by] ?? null) : null; $isIPD = $visit && $visit->visit_type === 'ipd'; $isUrgent = $visit && (($visit->urgency ?? '') === 'urgent'); // Per-row stock summary (built in the controller). $stockSummary = $queueStockByGroup[$groupKey] ?? ['ok' => 0, 'low' => 0]; $allInStock = $stockSummary['low'] === 0 && $stockSummary['ok'] > 0; // Row-level payment: ANY drug awaiting payment marks // the whole row unpaid (you can't dispense partial // pads until the patient pays). $isUnpaid = ($row->unpaid_count ?? 0) > 0; // Row-level status: worst-of across all pads on // this (patient, visit). Awaiting-payment is shown // via the Payment column, not duplicated here. if (($row->pending_count ?? 0) > 0) { $statusLabel = 'Pending'; $statusClass = 'badge-warning'; } elseif (($row->verified_count ?? 0) > 0) { $statusLabel = 'Verified'; $statusClass = 'badge-info'; } elseif (($row->ready_count ?? 0) > 0) { $statusLabel = 'Ready'; $statusClass = 'badge-success'; } else { $statusLabel = 'Pending'; $statusClass = 'badge-warning'; } $createdAt = \Carbon\Carbon::parse($row->created_at); @endphp @empty @endforelse
Time Patient Patient No. Type Prescriber Payment Stock Status Action
{{ $createdAt->format('H:i') }}

{{ $patient->full_name ?? '–' }}

{{-- Drug count moved inline under the name — cheap acknowledgement that this row may cover multiple pads, without re-introducing a Drugs column. --}}

{{ $row->drug_count }} drug{{ $row->drug_count == 1 ? '' : 's' }}@if(($row->pad_count ?? 1) > 1) · {{ $row->pad_count }} pads @endif

{{ $patient->mrn ?? '' }} {{ $isIPD ? 'IPD' : 'OPD' }} {{ $user->name ?? '' }} @if($isUnpaid) Unpaid @else Cleared @endif @if($allInStock) All In Stock @elseif($stockSummary['ok'] > 0 && $stockSummary['low'] > 0) {{ $stockSummary['low'] }} Low @else Low @endif {{ $statusLabel }} @if((int) ($row->visit_id ?? 0) > 0) Open @else @endif
No pending prescriptions in queue.
@if(isset($pendingRx) && $pendingRx->hasPages())
{{ $pendingRx->links() }}
@endif
{{-- TAB 2: RETURNS --}}

New Return

@csrf
@forelse($returns ?? [] as $ret) @empty @endforelse
Return #ItemQtyConditionReasonAction TakenByDate
{{ $ret->return_number ?? $ret->id }}{{ $ret->drug_name ?? '' }}{{ $ret->quantity }}{{ ucfirst($ret->condition ?? '–') }}{{ Str::limit($ret->reason, 30) }}{{ str_replace('_', ' ', ucfirst($ret->outcome ?? '')) }}{{ $ret->processor->name ?? '' }}{{ $ret->created_at->format('d M H:i') }}
No returns recorded.
{{-- TAB 3: PARTIAL / PENDING --}}
@forelse($partials ?? [] as $p) @php $qtyDispensed = \App\Models\PharmacyDispense::where('prescription_id', $p->id)->whereNull('reversed_at')->sum('quantity_dispensed'); @endphp @empty @endforelse
PatientDrugQty PrescribedQty DispensedBalanceReasonStatusDateAction
{{ $p->patient->full_name ?? '' }}{{ $p->drug_name }}{{ $p->quantity }}{{ $qtyDispensed }}{{ $p->quantity - $qtyDispensed }}Insufficient stockPartial{{ $p->updated_at->format('d M') }}Dispense Balance
No partial or pending items.
{{-- TAB 4: PRESCRIPTIONS — external only ──────────────────────────── In-house prescriptions are handled via the Queue tab and the Dispensed Drugs page. This tab is the home for EXTERNAL prescriptions (drugs we don't stock; patient fills outside) so they don't get lost between systems. --}}
@php // Roles allowed to create a new external prescription from // this tab. Matches the create/store route middleware so the // button never leads to a 403. $canCreateRx = Auth::user()->hasAnyRole('Super Admin', 'Hospital Admin', 'Clinician', 'Nurse'); @endphp @if($canCreateRx) @endif
@forelse($externalRx ?? [] as $row) @php $groupId = $row->prescription_group_id; $isLegacy = empty($groupId); $patient = $externalPatients[$row->patient_id] ?? null; $user = $externalPrescribers[$row->prescribed_by] ?? null; $created = \Carbon\Carbon::parse($row->created_at); @endphp @empty @endforelse
View Dispensed Drugs Date Patient MRN Drugs Prescriber Action
@if($isLegacy) @else {{ $groupId }} @endif {{ $created->format('d M Y') }}
{{ $created->format('H:i') }}
{{ $patient->full_name ?? '—' }} {{ $patient->mrn ?? '' }}

{{ $row->drug_count }} drug{{ $row->drug_count == 1 ? '' : 's' }}

{{ \Illuminate\Support\Str::limit($row->drug_names, 80) }}

{{ $user->name ?? '—' }} @if($isLegacy) Legacy @else View / Print @endif

No external prescriptions yet

External prescriptions are for drugs the patient fills outside the hospital.

@if(isset($externalRx) && $externalRx->hasPages())
{{ $externalRx->links() }}
@endif
{{-- TAB 5: OTC SALES HISTORY ───────────────────────────────────────── All over-the-counter dispenses (no prescription). Lives here so the team has one place to review sales without leaving the pharmacy workspace. The OTC sale form itself is still on the "OTC Sales" sidebar entry. Filter inputs use otc_ prefixed names so they don't clash with other tabs' filters when both have params in the same URL. --}}
@if(request()->hasAny(['otc_from', 'otc_to', 'otc_method', 'otc_search'])) Clear @endif
@forelse($otcSales as $sale) @empty @endforelse
Date / Time Drug Patient Qty Unit Price Total Method Dispensed By
{{ $sale->dispensed_at ? \Carbon\Carbon::parse($sale->dispensed_at)->format('d M H:i') : '—' }} {{ $sale->drugItem->name ?? '—' }} @if($sale->patient_id && $sale->patient) {{ $sale->patient->full_name }}

{{ $sale->patient->mrn }}

@else Walk-in @endif
{{ $sale->quantity_dispensed }} {{ number_format($sale->unit_price, 2) }} {{ number_format($sale->total_price, 2) }} {{ str_replace('_', ' ', $sale->payment_mode) }} {{ $sale->dispensedBy?->name ?? '—' }}
No OTC sales in this period.
@if($otcSales->hasPages())
{{ $otcSales->links() }}
@endif
@endsection @section('scripts') @include('partials._tab-state-helper', ['defaultTab' => 'queue']) @endsection