*/ public function dashboardStats(string $ownerRef, int $organizationId, ?int $branchId = null): array { $today = now()->startOfDay(); $patientsToday = Patient::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereDate('created_at', $today) ->count(); $appointmentsToday = Appointment::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereDate('scheduled_at', $today) ->count(); $openBills = Bill::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereIn('status', [Bill::STATUS_OPEN, Bill::STATUS_PARTIAL]) ->count(); $revenueToday = Payment::owned($ownerRef) ->whereHas('bill', function (Builder $q) use ($organizationId, $branchId) { $q->where('organization_id', $organizationId); if ($branchId) { $q->where('branch_id', $branchId); } }) ->whereDate('paid_at', $today) ->sum('amount_minor'); $pendingLab = InvestigationRequest::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereIn('status', InvestigationRequest::activeStatuses()) ->count(); return [ 'patients_today' => $patientsToday, 'appointments_today' => $appointmentsToday, 'open_bills' => $openBills, 'revenue_today_minor' => (int) $revenueToday, 'pending_lab' => $pendingLab, ]; } /** * @return array */ public function patientsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $newPatients = Patient::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('created_at', [$from, $to]) ->count(); $returningPatients = Visit::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('checked_in_at', [$from, $to]) ->distinct('patient_id') ->count('patient_id'); $visits = Visit::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('checked_in_at', [$from, $to]) ->count(); return [ 'new_patients' => $newPatients, 'returning_patients' => $returningPatients, 'total_visits' => $visits, ]; } /** * @return array */ public function appointmentsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $base = Appointment::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('scheduled_at', [$from, $to]); return [ 'total' => (clone $base)->count(), 'completed' => (clone $base)->where('status', Appointment::STATUS_COMPLETED)->count(), 'cancelled' => (clone $base)->where('status', Appointment::STATUS_CANCELLED)->count(), 'no_show' => (clone $base)->where('status', Appointment::STATUS_NO_SHOW)->count(), ]; } /** * @return array */ public function laboratoryReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $base = InvestigationRequest::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('created_at', [$from, $to]); $completed = (clone $base) ->whereIn('status', [InvestigationRequest::STATUS_COMPLETED, InvestigationRequest::STATUS_DELIVERED]) ->get(); $turnaroundHours = $completed ->filter(fn ($r) => $r->completed_at && $r->created_at) ->map(fn ($r) => $r->created_at->diffInHours($r->completed_at)) ->avg(); return [ 'requested' => (clone $base)->count(), 'completed' => $completed->count(), 'pending' => (clone $base)->whereIn('status', InvestigationRequest::activeStatuses())->count(), 'avg_turnaround_hours' => $turnaroundHours ? round($turnaroundHours, 1) : null, ]; } /** * @return array */ public function financeReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array { $billed = Bill::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('created_at', [$from, $to]) ->where('status', '!=', Bill::STATUS_VOID); $payments = Payment::owned($ownerRef) ->whereHas('bill', function (Builder $q) use ($organizationId, $branchId) { $q->where('organization_id', $organizationId); if ($branchId) { $q->where('branch_id', $branchId); } }) ->whereBetween('paid_at', [$from, $to]); return [ 'invoiced_minor' => (int) (clone $billed)->sum('total_minor'), 'collected_minor' => (int) (clone $payments)->sum('amount_minor'), 'outstanding_minor' => (int) Bill::owned($ownerRef) ->where('organization_id', $organizationId) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereIn('status', [Bill::STATUS_OPEN, Bill::STATUS_PARTIAL]) ->sum('balance_minor'), 'invoice_count' => (clone $billed)->count(), ]; } /** * @return Collection */ public function clinicalReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to): Collection { return Diagnosis::owned($ownerRef) ->whereHas('consultation.visit', function (Builder $q) use ($organizationId, $from, $to) { $q->where('organization_id', $organizationId) ->whereBetween('checked_in_at', [$from, $to]); }) ->select('description', DB::raw('count(*) as total')) ->groupBy('description') ->orderByDesc('total') ->limit(20) ->get(); } }