startOfDay(); $rangeFrom = $from ? Carbon::parse($from)->startOfDay() : now()->startOfMonth(); $rangeTo = $to ? Carbon::parse($to)->endOfDay() : now()->endOfDay(); $departmentIds = $this->shell->departmentIds($organization, 'physiotherapy', $ownerRef, $branchId); $appointmentBase = Appointment::owned($ownerRef) ->where('organization_id', $organization->id) ->when($departmentIds !== [], fn ($q) => $q->whereIn('department_id', $departmentIds)) ->when($departmentIds === [], fn ($q) => $q->whereRaw('1 = 0')) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)); $arrivalsToday = (clone $appointmentBase) ->where('checked_in_at', '>=', $todayStart) ->count(); $completedToday = (clone $appointmentBase) ->where('status', Appointment::STATUS_COMPLETED) ->where('completed_at', '>=', $todayStart) ->count(); $visitIds = (clone $appointmentBase)->whereNotNull('visit_id')->pluck('visit_id'); $openVisitIds = Visit::owned($ownerRef) ->where('organization_id', $organization->id) ->whereIn('id', $visitIds->isEmpty() ? [0] : $visitIds) ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) ->pluck('id'); $highPainOpen = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) ->where('module_key', 'physiotherapy') ->where('record_type', 'pt_assessment') ->whereIn('visit_id', $openVisitIds->isEmpty() ? [0] : $openVisitIds) ->get() ->filter(fn (SpecialtyClinicalRecord $r) => (int) ($r->payload['pain_score'] ?? 0) >= 8) ->count(); $planRecords = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) ->where('module_key', 'physiotherapy') ->where('record_type', 'pt_plan') ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('recorded_at', [$rangeFrom, $rangeTo]) ->get(); $diagnosisBreakdown = $planRecords ->groupBy(fn (SpecialtyClinicalRecord $r) => (string) ($r->payload['diagnosis'] ?? 'Unknown')) ->map(fn (Collection $rows, string $diagnosis) => [ 'diagnosis' => $diagnosis, 'total' => $rows->count(), ]) ->values() ->sortByDesc('total') ->values(); $sessionRecords = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) ->where('module_key', 'physiotherapy') ->where('record_type', 'pt_session') ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('recorded_at', [$rangeFrom, $rangeTo]) ->get(); $sessionOutcomes = $sessionRecords ->groupBy(fn (SpecialtyClinicalRecord $r) => (string) ($r->payload['outcome'] ?? 'Unknown')) ->map(fn (Collection $rows, string $outcome) => [ 'outcome' => $outcome, 'total' => $rows->count(), ]) ->values() ->sortByDesc('total') ->values(); $completedVisits = Visit::owned($ownerRef) ->where('organization_id', $organization->id) ->whereIn('id', $visitIds->isEmpty() ? [0] : $visitIds) ->where('status', Visit::STATUS_COMPLETED) ->whereBetween('completed_at', [$rangeFrom, $rangeTo]) ->whereNotNull('checked_in_at') ->whereNotNull('completed_at') ->get(); $avgLos = null; if ($completedVisits->isNotEmpty()) { $avgLos = round($completedVisits->avg(function (Visit $visit) { return $visit->checked_in_at->diffInMinutes($visit->completed_at); }), 1); } $serviceLabels = collect($this->shell->provisionedServices($organization, 'physiotherapy')) ->pluck('label') ->filter() ->values() ->all(); $revenueByService = BillLineItem::query() ->where('owner_ref', $ownerRef) ->where('source_type', 'specialty_service') ->whereIn('description', $serviceLabels ?: ['__none__']) ->whereBetween('created_at', [$rangeFrom, $rangeTo]) ->whereHas('bill', function ($q) use ($organization, $visitIds) { $q->where('organization_id', $organization->id) ->whereIn('visit_id', $visitIds->isEmpty() ? [0] : $visitIds) ->whereNotIn('status', [Bill::STATUS_VOID]); }) ->selectRaw('description, count(*) as total, sum(total_minor) as amount_minor') ->groupBy('description') ->orderByDesc('amount_minor') ->get(); return [ 'arrivals_today' => $arrivalsToday, 'completed_today' => $completedToday, 'high_pain_open' => $highPainOpen, 'diagnosis_breakdown' => $diagnosisBreakdown, 'session_outcomes' => $sessionOutcomes, 'avg_los_minutes' => $avgLos, 'revenue_by_service' => $revenueByService, 'from' => $rangeFrom->toDateString(), 'to' => $rangeTo->toDateString(), ]; } }